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

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

214 statements  

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

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

3 

4""" Read SPE files. 

5 

6This plugin supports reading files saved in the Princeton Instruments 

7SPE file format. 

8 

9Parameters 

10---------- 

11check_filesize : bool 

12 The number of frames in the file is stored in the file header. However, 

13 this number may be wrong for certain software. If this is `True` 

14 (default), derive the number of frames also from the file size and 

15 raise a warning if the two values do not match. 

16char_encoding : str 

17 Deprecated. Exists for backwards compatibility; use ``char_encoding`` of 

18 ``metadata`` instead. 

19sdt_meta : bool 

20 Deprecated. Exists for backwards compatibility; use ``sdt_control`` of 

21 ``metadata`` instead. 

22 

23Methods 

24------- 

25.. note:: 

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

27 documentation. 

28 

29.. autosummary:: 

30 :toctree: 

31 

32 SpePlugin.read 

33 SpePlugin.iter 

34 SpePlugin.properties 

35 SpePlugin.metadata 

36 

37""" 

38 

39from datetime import datetime 

40import logging 

41import os 

42from typing import ( 

43 Any, 

44 Callable, 

45 Dict, 

46 Iterator, 

47 List, 

48 Mapping, 

49 Optional, 

50 Sequence, 

51 Tuple, 

52 Union, 

53) 

54import warnings 

55 

56import numpy as np 

57 

58from ..core.request import Request, IOMode, InitializationError 

59from ..core.v3_plugin_api import PluginV3, ImageProperties 

60 

61 

62logger = logging.getLogger(__name__) 

63 

64 

65class Spec: 

66 """SPE file specification data 

67 

68 Tuples of (offset, datatype, count), where offset is the offset in the SPE 

69 file and datatype is the datatype as used in `numpy.fromfile`() 

70 

71 `data_start` is the offset of actual image data. 

72 

73 `dtypes` translates SPE datatypes (0...4) to numpy ones, e. g. dtypes[0] 

74 is dtype("<f") (which is np.float32). 

75 

76 `controllers` maps the `type` metadata to a human readable name 

77 

78 `readout_modes` maps the `readoutMode` metadata to something human readable 

79 although this may not be accurate since there is next to no documentation 

80 to be found. 

81 """ 

82 

83 basic = { 

84 "datatype": (108, "<h"), # dtypes 

85 "xdim": (42, "<H"), 

86 "ydim": (656, "<H"), 

87 "xml_footer_offset": (678, "<Q"), 

88 "NumFrames": (1446, "<i"), 

89 "file_header_ver": (1992, "<f"), 

90 } 

91 

92 metadata = { 

93 # ROI information 

94 "NumROI": (1510, "<h"), 

95 "ROIs": ( 

96 1512, 

97 np.dtype( 

98 [ 

99 ("startx", "<H"), 

100 ("endx", "<H"), 

101 ("groupx", "<H"), 

102 ("starty", "<H"), 

103 ("endy", "<H"), 

104 ("groupy", "<H"), 

105 ] 

106 ), 

107 10, 

108 ), 

109 # chip-related sizes 

110 "xDimDet": (6, "<H"), 

111 "yDimDet": (18, "<H"), 

112 "VChipXdim": (14, "<h"), 

113 "VChipYdim": (16, "<h"), 

114 # other stuff 

115 "controller_version": (0, "<h"), 

116 "logic_output": (2, "<h"), 

117 "amp_high_cap_low_noise": (4, "<H"), # enum? 

118 "mode": (8, "<h"), # enum? 

119 "exposure_sec": (10, "<f"), 

120 "date": (20, "<10S"), 

121 "detector_temp": (36, "<f"), 

122 "detector_type": (40, "<h"), 

123 "st_diode": (44, "<h"), 

124 "delay_time": (46, "<f"), 

125 # shutter_control: normal, disabled open, disabled closed 

126 # But which one is which? 

127 "shutter_control": (50, "<H"), 

128 "absorb_live": (52, "<h"), 

129 "absorb_mode": (54, "<H"), 

130 "can_do_virtual_chip": (56, "<h"), 

131 "threshold_min_live": (58, "<h"), 

132 "threshold_min_val": (60, "<f"), 

133 "threshold_max_live": (64, "<h"), 

134 "threshold_max_val": (66, "<f"), 

135 "time_local": (172, "<7S"), 

136 "time_utc": (179, "<7S"), 

137 "adc_offset": (188, "<H"), 

138 "adc_rate": (190, "<H"), 

139 "adc_type": (192, "<H"), 

140 "adc_resolution": (194, "<H"), 

141 "adc_bit_adjust": (196, "<H"), 

142 "gain": (198, "<H"), 

143 "comments": (200, "<80S", 5), 

144 "geometric": (600, "<H"), # flags 

145 "sw_version": (688, "<16S"), 

146 "spare_4": (742, "<436S"), 

147 "XPrePixels": (98, "<h"), 

148 "XPostPixels": (100, "<h"), 

149 "YPrePixels": (102, "<h"), 

150 "YPostPixels": (104, "<h"), 

151 "readout_time": (672, "<f"), 

152 "xml_footer_offset": (678, "<Q"), 

153 "type": (704, "<h"), # controllers 

154 "clockspeed_us": (1428, "<f"), 

155 "readout_mode": (1480, "<H"), # readout_modes 

156 "window_size": (1482, "<H"), 

157 "file_header_ver": (1992, "<f"), 

158 } 

159 

160 data_start = 4100 

161 

162 dtypes = { 

163 0: np.dtype(np.float32), 

164 1: np.dtype(np.int32), 

165 2: np.dtype(np.int16), 

166 3: np.dtype(np.uint16), 

167 8: np.dtype(np.uint32), 

168 } 

169 

170 controllers = [ 

171 "new120 (Type II)", 

172 "old120 (Type I)", 

173 "ST130", 

174 "ST121", 

175 "ST138", 

176 "DC131 (PentaMax)", 

177 "ST133 (MicroMax/Roper)", 

178 "ST135 (GPIB)", 

179 "VTCCD", 

180 "ST116 (GPIB)", 

181 "OMA3 (GPIB)", 

182 "OMA4", 

183 ] 

184 

185 # This was gathered from random places on the internet and own experiments 

186 # with the camera. May not be accurate. 

187 readout_modes = ["full frame", "frame transfer", "kinetics"] 

188 

189 # Do not decode the following metadata keys into strings, but leave them 

190 # as byte arrays 

191 no_decode = ["spare_4"] 

192 

193 

194class SDTControlSpec: 

195 """Extract metadata written by the SDT-control software 

196 

197 Some of it is encoded in the comment strings 

198 (see :py:meth:`parse_comments`). Also, date and time are encoded in a 

199 peculiar way (see :py:meth:`get_datetime`). Use :py:meth:`extract_metadata` 

200 to update the metadata dict. 

201 """ 

202 

203 months = { 

204 # Convert SDT-control month strings to month numbers 

205 "Jän": 1, 

206 "Jan": 1, 

207 "Feb": 2, 

208 "Mär": 3, 

209 "Mar": 3, 

210 "Apr": 4, 

211 "Mai": 5, 

212 "May": 5, 

213 "Jun": 6, 

214 "Jul": 7, 

215 "Aug": 8, 

216 "Sep": 9, 

217 "Okt": 10, 

218 "Oct": 10, 

219 "Nov": 11, 

220 "Dez": 12, 

221 "Dec": 12, 

222 } 

223 

224 sequence_types = { 

225 # TODO: complete 

226 "SEQU": "standard", 

227 "SETO": "TOCCSL", 

228 "KINE": "kinetics", 

229 "SEAR": "arbitrary", 

230 } 

231 

232 class CommentDesc: 

233 """Describe how to extract a metadata entry from a comment string""" 

234 

235 n: int 

236 """Which of the 5 SPE comment fields to use.""" 

237 slice: slice 

238 """Which characters from the `n`-th comment to use.""" 

239 cvt: Callable[[str], Any] 

240 """How to convert characters to something useful.""" 

241 scale: Union[None, float] 

242 """Optional scaling factor for numbers""" 

243 

244 def __init__( 

245 self, 

246 n: int, 

247 slice: slice, 

248 cvt: Callable[[str], Any] = str, 

249 scale: Optional[float] = None, 

250 ): 

251 self.n = n 

252 self.slice = slice 

253 self.cvt = cvt 

254 self.scale = scale 

255 

256 comment_fields = { 

257 (5, 0): { 

258 "sdt_major_version": CommentDesc(4, slice(66, 68), int), 

259 "sdt_minor_version": CommentDesc(4, slice(68, 70), int), 

260 "sdt_controller_name": CommentDesc(4, slice(0, 6), str), 

261 "exposure_time": CommentDesc(1, slice(64, 73), float, 10**-6), 

262 "color_code": CommentDesc(4, slice(10, 14), str), 

263 "detection_channels": CommentDesc(4, slice(15, 16), int), 

264 "background_subtraction": CommentDesc(4, 14, lambda x: x == "B"), 

265 "em_active": CommentDesc(4, 32, lambda x: x == "E"), 

266 "em_gain": CommentDesc(4, slice(28, 32), int), 

267 "modulation_active": CommentDesc(4, 33, lambda x: x == "A"), 

268 "pixel_size": CommentDesc(4, slice(25, 28), float, 0.1), 

269 "sequence_type": CommentDesc( 

270 4, slice(6, 10), lambda x: __class__.sequence_types[x] 

271 ), 

272 "grid": CommentDesc(4, slice(16, 25), float, 10**-6), 

273 "n_macro": CommentDesc(1, slice(0, 4), int), 

274 "delay_macro": CommentDesc(1, slice(10, 19), float, 10**-3), 

275 "n_mini": CommentDesc(1, slice(4, 7), int), 

276 "delay_mini": CommentDesc(1, slice(19, 28), float, 10**-6), 

277 "n_micro": CommentDesc(1, slice(7, 10), int), 

278 "delay_micro": CommentDesc(1, slice(28, 37), float, 10**-6), 

279 "n_subpics": CommentDesc(1, slice(7, 10), int), 

280 "delay_shutter": CommentDesc(1, slice(73, 79), float, 10**-6), 

281 "delay_prebleach": CommentDesc(1, slice(37, 46), float, 10**-6), 

282 "bleach_time": CommentDesc(1, slice(46, 55), float, 10**-6), 

283 "recovery_time": CommentDesc(1, slice(55, 64), float, 10**-6), 

284 }, 

285 (5, 1): { 

286 "bleach_piezo_active": CommentDesc(4, slice(34, 35), lambda x: x == "z") 

287 }, 

288 } 

289 

290 @staticmethod 

291 def get_comment_version(comments: Sequence[str]) -> Tuple[int, int]: 

292 """Get the version of SDT-control metadata encoded in the comments 

293 

294 Parameters 

295 ---------- 

296 comments 

297 List of SPE file comments, typically ``metadata["comments"]``. 

298 

299 Returns 

300 ------- 

301 Major and minor version. ``-1, -1`` if detection failed. 

302 """ 

303 if comments[4][70:76] != "COMVER": 

304 return -1, -1 

305 try: 

306 return int(comments[4][76:78]), int(comments[4][78:80]) 

307 except ValueError: 

308 return -1, -1 

309 

310 @staticmethod 

311 def parse_comments( 

312 comments: Sequence[str], version: Tuple[int, int] 

313 ) -> Dict[str, Any]: 

314 """Extract SDT-control metadata from comments 

315 

316 Parameters 

317 ---------- 

318 comments 

319 List of SPE file comments, typically ``metadata["comments"]``. 

320 version 

321 Major and minor version of SDT-control metadata format 

322 

323 Returns 

324 ------- 

325 Dict of metadata 

326 """ 

327 sdt_md = {} 

328 for minor in range(version[1] + 1): 

329 # Metadata with same major version is backwards compatible. 

330 # Fields are specified incrementally in `comment_fields`. 

331 # E.g. if the file has version 5.01, `comment_fields[5, 0]` and 

332 # `comment_fields[5, 1]` need to be decoded. 

333 try: 

334 cmt = __class__.comment_fields[version[0], minor] 

335 except KeyError: 

336 continue 

337 for name, spec in cmt.items(): 

338 try: 

339 v = spec.cvt(comments[spec.n][spec.slice]) 

340 if spec.scale is not None: 

341 v *= spec.scale 

342 sdt_md[name] = v 

343 except Exception as e: 

344 warnings.warn( 

345 f"Failed to decode SDT-control metadata field `{name}`: {e}" 

346 ) 

347 sdt_md[name] = None 

348 if version not in __class__.comment_fields: 

349 supported_ver = ", ".join( 

350 map(lambda x: f"{x[0]}.{x[1]:02}", __class__.comment_fields) 

351 ) 

352 warnings.warn( 

353 f"Unsupported SDT-control metadata version {version[0]}.{version[1]:02}. " 

354 f"Only versions {supported_ver} are supported. " 

355 "Some or all SDT-control metadata may be missing." 

356 ) 

357 comment = comments[0] + comments[2] 

358 sdt_md["comment"] = comment.strip() 

359 return sdt_md 

360 

361 @staticmethod 

362 def get_datetime(date: str, time: str) -> Union[datetime, None]: 

363 """Turn date and time saved by SDT-control into proper datetime object 

364 

365 Parameters 

366 ---------- 

367 date 

368 SPE file date, typically ``metadata["date"]``. 

369 time 

370 SPE file date, typically ``metadata["time_local"]``. 

371 

372 Returns 

373 ------- 

374 File's datetime if parsing was succsessful, else None. 

375 """ 

376 try: 

377 month = __class__.months[date[2:5]] 

378 return datetime( 

379 int(date[5:9]), 

380 month, 

381 int(date[0:2]), 

382 int(time[0:2]), 

383 int(time[2:4]), 

384 int(time[4:6]), 

385 ) 

386 except Exception as e: 

387 logger.info(f"Failed to decode date from SDT-control metadata: {e}.") 

388 

389 @staticmethod 

390 def extract_metadata(meta: Mapping, char_encoding: str = "latin1"): 

391 """Extract SDT-control metadata from SPE metadata 

392 

393 SDT-control stores some metadata in comments and other fields. 

394 Extract them and remove unused entries. 

395 

396 Parameters 

397 ---------- 

398 meta 

399 SPE file metadata. Modified in place. 

400 char_encoding 

401 Character encoding used to decode strings in the metadata. 

402 """ 

403 comver = __class__.get_comment_version(meta["comments"]) 

404 if any(c < 0 for c in comver): 

405 # This file most likely was not created by SDT-control 

406 logger.debug("SDT-control comments not found.") 

407 return 

408 

409 sdt_meta = __class__.parse_comments(meta["comments"], comver) 

410 meta.pop("comments") 

411 meta.update(sdt_meta) 

412 

413 # Get date and time in a usable format 

414 dt = __class__.get_datetime(meta["date"], meta["time_local"]) 

415 if dt: 

416 meta["datetime"] = dt 

417 meta.pop("date") 

418 meta.pop("time_local") 

419 

420 sp4 = meta["spare_4"] 

421 try: 

422 meta["modulation_script"] = sp4.decode(char_encoding) 

423 meta.pop("spare_4") 

424 except UnicodeDecodeError: 

425 warnings.warn( 

426 "Failed to decode SDT-control laser " 

427 "modulation script. Bad char_encoding?" 

428 ) 

429 

430 # Get rid of unused data 

431 meta.pop("time_utc") 

432 meta.pop("exposure_sec") 

433 

434 

435class SpePlugin(PluginV3): 

436 def __init__( 

437 self, 

438 request: Request, 

439 check_filesize: bool = True, 

440 char_encoding: Optional[str] = None, 

441 sdt_meta: Optional[bool] = None, 

442 ) -> None: 

443 """Instantiate a new SPE file plugin object 

444 

445 Parameters 

446 ---------- 

447 request : Request 

448 A request object representing the resource to be operated on. 

449 check_filesize : bool 

450 If True, compute the number of frames from the filesize, compare it 

451 to the frame count in the file header, and raise a warning if the 

452 counts don't match. (Certain software may create files with 

453 char_encoding : str 

454 Deprecated. Exists for backwards compatibility; use ``char_encoding`` of 

455 ``metadata`` instead. 

456 sdt_meta : bool 

457 Deprecated. Exists for backwards compatibility; use ``sdt_control`` of 

458 ``metadata`` instead. 

459 

460 """ 

461 

462 super().__init__(request) 

463 if request.mode.io_mode == IOMode.write: 

464 raise InitializationError("cannot write SPE files") 

465 

466 if char_encoding is not None: 

467 warnings.warn( 

468 "Passing `char_encoding` to the constructor is deprecated. " 

469 "Use `char_encoding` parameter of the `metadata()` method " 

470 "instead.", 

471 DeprecationWarning, 

472 ) 

473 self._char_encoding = char_encoding 

474 if sdt_meta is not None: 

475 warnings.warn( 

476 "Passing `sdt_meta` to the constructor is deprecated. " 

477 "Use `sdt_control` parameter of the `metadata()` method " 

478 "instead.", 

479 DeprecationWarning, 

480 ) 

481 self._sdt_meta = sdt_meta 

482 

483 self._file = self.request.get_file() 

484 

485 try: 

486 # Spec.basic contains no string, no need to worry about character 

487 # encoding. 

488 info = self._parse_header(Spec.basic, "latin1") 

489 self._file_header_ver = info["file_header_ver"] 

490 self._dtype = Spec.dtypes[info["datatype"]] 

491 self._shape = (info["ydim"], info["xdim"]) 

492 self._len = info["NumFrames"] 

493 

494 if check_filesize: 

495 # Some software writes incorrect `NumFrames` metadata. 

496 # To determine the number of frames, check the size of the data 

497 # segment -- until the end of the file for SPE<3, until the 

498 # xml footer for SPE>=3. 

499 if info["file_header_ver"] >= 3: 

500 data_end = info["xml_footer_offset"] 

501 else: 

502 self._file.seek(0, os.SEEK_END) 

503 data_end = self._file.tell() 

504 line = data_end - Spec.data_start 

505 line //= self._shape[0] * self._shape[1] * self._dtype.itemsize 

506 if line != self._len: 

507 warnings.warn( 

508 f"The file header of {self.request.filename} claims there are " 

509 f"{self._len} frames, but there are actually {line} frames." 

510 ) 

511 self._len = min(line, self._len) 

512 self._file.seek(Spec.data_start) 

513 except Exception: 

514 raise InitializationError("SPE plugin cannot read the provided file.") 

515 

516 def read(self, *, index: int = ...) -> np.ndarray: 

517 """Read a frame or all frames from the file 

518 

519 Parameters 

520 ---------- 

521 index : int 

522 Select the index-th frame from the file. If index is `...`, 

523 select all frames and stack them along a new axis. 

524 

525 Returns 

526 ------- 

527 A Numpy array of pixel values. 

528 

529 """ 

530 

531 if index is Ellipsis: 

532 read_offset = Spec.data_start 

533 count = self._shape[0] * self._shape[1] * self._len 

534 out_shape = (self._len, *self._shape) 

535 elif index < 0: 

536 raise IndexError(f"Index `{index}` is smaller than 0.") 

537 elif index >= self._len: 

538 raise IndexError( 

539 f"Index `{index}` exceeds the number of frames stored in this file (`{self._len}`)." 

540 ) 

541 else: 

542 read_offset = ( 

543 Spec.data_start 

544 + index * self._shape[0] * self._shape[1] * self._dtype.itemsize 

545 ) 

546 count = self._shape[0] * self._shape[1] 

547 out_shape = self._shape 

548 

549 self._file.seek(read_offset) 

550 data = np.fromfile(self._file, dtype=self._dtype, count=count) 

551 return data.reshape(out_shape) 

552 

553 def iter(self) -> Iterator[np.ndarray]: 

554 """Iterate over the frames in the file 

555 

556 Yields 

557 ------ 

558 A Numpy array of pixel values. 

559 """ 

560 

561 return (self.read(index=i) for i in range(self._len)) 

562 

563 def metadata( 

564 self, 

565 index: int = ..., 

566 exclude_applied: bool = True, 

567 char_encoding: str = "latin1", 

568 sdt_control: bool = True, 

569 ) -> Dict[str, Any]: 

570 """SPE specific metadata. 

571 

572 Parameters 

573 ---------- 

574 index : int 

575 Ignored as SPE files only store global metadata. 

576 exclude_applied : bool 

577 Ignored. Exists for API compatibility. 

578 char_encoding : str 

579 The encoding to use when parsing strings. 

580 sdt_control : bool 

581 If `True`, decode special metadata written by the 

582 SDT-control software if present. 

583 

584 Returns 

585 ------- 

586 metadata : dict 

587 Key-value pairs of metadata. 

588 

589 Notes 

590 ----- 

591 SPE v3 stores metadata as XML, whereas SPE v2 uses a binary format. 

592 

593 .. rubric:: Supported SPE v2 Metadata fields 

594 

595 ROIs : list of dict 

596 Regions of interest used for recording images. Each dict has the 

597 "top_left" key containing x and y coordinates of the top left corner, 

598 the "bottom_right" key with x and y coordinates of the bottom right 

599 corner, and the "bin" key with number of binned pixels in x and y 

600 directions. 

601 comments : list of str 

602 The SPE format allows for 5 comment strings of 80 characters each. 

603 controller_version : int 

604 Hardware version 

605 logic_output : int 

606 Definition of output BNC 

607 amp_hi_cap_low_noise : int 

608 Amp switching mode 

609 mode : int 

610 Timing mode 

611 exp_sec : float 

612 Alternative exposure in seconds 

613 date : str 

614 Date string 

615 detector_temp : float 

616 Detector temperature 

617 detector_type : int 

618 CCD / diode array type 

619 st_diode : int 

620 Trigger diode 

621 delay_time : float 

622 Used with async mode 

623 shutter_control : int 

624 Normal, disabled open, or disabled closed 

625 absorb_live : bool 

626 on / off 

627 absorb_mode : int 

628 Reference strip or file 

629 can_do_virtual_chip : bool 

630 True or False whether chip can do virtual chip 

631 threshold_min_live : bool 

632 on / off 

633 threshold_min_val : float 

634 Threshold minimum value 

635 threshold_max_live : bool 

636 on / off 

637 threshold_max_val : float 

638 Threshold maximum value 

639 time_local : str 

640 Experiment local time 

641 time_utc : str 

642 Experiment UTC time 

643 adc_offset : int 

644 ADC offset 

645 adc_rate : int 

646 ADC rate 

647 adc_type : int 

648 ADC type 

649 adc_resolution : int 

650 ADC resolution 

651 adc_bit_adjust : int 

652 ADC bit adjust 

653 gain : int 

654 gain 

655 sw_version : str 

656 Version of software which created this file 

657 spare_4 : bytes 

658 Reserved space 

659 readout_time : float 

660 Experiment readout time 

661 type : str 

662 Controller type 

663 clockspeed_us : float 

664 Vertical clock speed in microseconds 

665 readout_mode : ["full frame", "frame transfer", "kinetics", ""] 

666 Readout mode. Empty string means that this was not set by the 

667 Software. 

668 window_size : int 

669 Window size for Kinetics mode 

670 file_header_ver : float 

671 File header version 

672 chip_size : [int, int] 

673 x and y dimensions of the camera chip 

674 virt_chip_size : [int, int] 

675 Virtual chip x and y dimensions 

676 pre_pixels : [int, int] 

677 Pre pixels in x and y dimensions 

678 post_pixels : [int, int], 

679 Post pixels in x and y dimensions 

680 geometric : list of {"rotate", "reverse", "flip"} 

681 Geometric operations 

682 sdt_major_version : int 

683 (only for files created by SDT-control) 

684 Major version of SDT-control software 

685 sdt_minor_version : int 

686 (only for files created by SDT-control) 

687 Minor version of SDT-control software 

688 sdt_controller_name : str 

689 (only for files created by SDT-control) 

690 Controller name 

691 exposure_time : float 

692 (only for files created by SDT-control) 

693 Exposure time in seconds 

694 color_code : str 

695 (only for files created by SDT-control) 

696 Color channels used 

697 detection_channels : int 

698 (only for files created by SDT-control) 

699 Number of channels 

700 background_subtraction : bool 

701 (only for files created by SDT-control) 

702 Whether background subtraction war turned on 

703 em_active : bool 

704 (only for files created by SDT-control) 

705 Whether EM was turned on 

706 em_gain : int 

707 (only for files created by SDT-control) 

708 EM gain 

709 modulation_active : bool 

710 (only for files created by SDT-control) 

711 Whether laser modulation (“attenuate”) was turned on 

712 pixel_size : float 

713 (only for files created by SDT-control) 

714 Camera pixel size 

715 sequence_type : str 

716 (only for files created by SDT-control) 

717 Type of sequnce (standard, TOCCSL, arbitrary, …) 

718 grid : float 

719 (only for files created by SDT-control) 

720 Sequence time unit (“grid size”) in seconds 

721 n_macro : int 

722 (only for files created by SDT-control) 

723 Number of macro loops 

724 delay_macro : float 

725 (only for files created by SDT-control) 

726 Time between macro loops in seconds 

727 n_mini : int 

728 (only for files created by SDT-control) 

729 Number of mini loops 

730 delay_mini : float 

731 (only for files created by SDT-control) 

732 Time between mini loops in seconds 

733 n_micro : int (only for files created by SDT-control) 

734 Number of micro loops 

735 delay_micro : float (only for files created by SDT-control) 

736 Time between micro loops in seconds 

737 n_subpics : int 

738 (only for files created by SDT-control) 

739 Number of sub-pictures 

740 delay_shutter : float 

741 (only for files created by SDT-control) 

742 Camera shutter delay in seconds 

743 delay_prebleach : float 

744 (only for files created by SDT-control) 

745 Pre-bleach delay in seconds 

746 bleach_time : float 

747 (only for files created by SDT-control) 

748 Bleaching time in seconds 

749 recovery_time : float 

750 (only for files created by SDT-control) 

751 Recovery time in seconds 

752 comment : str 

753 (only for files created by SDT-control) 

754 User-entered comment. This replaces the "comments" field. 

755 datetime : datetime.datetime 

756 (only for files created by SDT-control) 

757 Combines the "date" and "time_local" keys. The latter two plus 

758 "time_utc" are removed. 

759 modulation_script : str 

760 (only for files created by SDT-control) 

761 Laser modulation script. Replaces the "spare_4" key. 

762 bleach_piezo_active : bool 

763 (only for files created by SDT-control) 

764 Whether piezo for bleaching was enabled 

765 """ 

766 

767 if self._file_header_ver < 3: 

768 if self._char_encoding is not None: 

769 char_encoding = self._char_encoding 

770 if self._sdt_meta is not None: 

771 sdt_control = self._sdt_meta 

772 return self._metadata_pre_v3(char_encoding, sdt_control) 

773 return self._metadata_post_v3() 

774 

775 def _metadata_pre_v3(self, char_encoding: str, sdt_control: bool) -> Dict[str, Any]: 

776 """Extract metadata from SPE v2 files 

777 

778 Parameters 

779 ---------- 

780 char_encoding 

781 String character encoding 

782 sdt_control 

783 If `True`, try to decode special metadata written by the 

784 SDT-control software. 

785 

786 Returns 

787 ------- 

788 dict mapping metadata names to values. 

789 

790 """ 

791 

792 m = self._parse_header(Spec.metadata, char_encoding) 

793 

794 nr = m.pop("NumROI", None) 

795 nr = 1 if nr < 1 else nr 

796 m["ROIs"] = roi_array_to_dict(m["ROIs"][:nr]) 

797 

798 # chip sizes 

799 m["chip_size"] = [m.pop(k, None) for k in ("xDimDet", "yDimDet")] 

800 m["virt_chip_size"] = [m.pop(k, None) for k in ("VChipXdim", "VChipYdim")] 

801 m["pre_pixels"] = [m.pop(k, None) for k in ("XPrePixels", "YPrePixels")] 

802 m["post_pixels"] = [m.pop(k, None) for k in ("XPostPixels", "YPostPixels")] 

803 

804 # convert comments from numpy.str_ to str 

805 m["comments"] = [str(c) for c in m["comments"]] 

806 

807 # geometric operations 

808 g = [] 

809 f = m.pop("geometric", 0) 

810 if f & 1: 

811 g.append("rotate") 

812 if f & 2: 

813 g.append("reverse") 

814 if f & 4: 

815 g.append("flip") 

816 m["geometric"] = g 

817 

818 # Make some additional information more human-readable 

819 t = m["type"] 

820 if 1 <= t <= len(Spec.controllers): 

821 m["type"] = Spec.controllers[t - 1] 

822 else: 

823 m["type"] = None 

824 r = m["readout_mode"] 

825 if 1 <= r <= len(Spec.readout_modes): 

826 m["readout_mode"] = Spec.readout_modes[r - 1] 

827 else: 

828 m["readout_mode"] = None 

829 

830 # bools 

831 for k in ( 

832 "absorb_live", 

833 "can_do_virtual_chip", 

834 "threshold_min_live", 

835 "threshold_max_live", 

836 ): 

837 m[k] = bool(m[k]) 

838 

839 # Extract SDT-control metadata if desired 

840 if sdt_control: 

841 SDTControlSpec.extract_metadata(m, char_encoding) 

842 

843 return m 

844 

845 def _metadata_post_v3(self) -> Dict[str, Any]: 

846 """Extract XML metadata from SPE v3 files 

847 

848 Returns 

849 ------- 

850 dict with key `"__xml"`, whose value is the XML metadata 

851 """ 

852 

853 info = self._parse_header(Spec.basic, "latin1") 

854 self._file.seek(info["xml_footer_offset"]) 

855 xml = self._file.read() 

856 return {"__xml": xml} 

857 

858 def properties(self, index: int = ...) -> ImageProperties: 

859 """Standardized ndimage metadata. 

860 

861 Parameters 

862 ---------- 

863 index : int 

864 If the index is an integer, select the index-th frame and return 

865 its properties. If index is an Ellipsis (...), return the 

866 properties of all frames in the file stacked along a new batch 

867 dimension. 

868 

869 Returns 

870 ------- 

871 properties : ImageProperties 

872 A dataclass filled with standardized image metadata. 

873 """ 

874 

875 if index is Ellipsis: 

876 return ImageProperties( 

877 shape=(self._len, *self._shape), 

878 dtype=self._dtype, 

879 n_images=self._len, 

880 is_batch=True, 

881 ) 

882 return ImageProperties(shape=self._shape, dtype=self._dtype, is_batch=False) 

883 

884 def _parse_header( 

885 self, spec: Mapping[str, Tuple], char_encoding: str 

886 ) -> Dict[str, Any]: 

887 """Get information from SPE file header 

888 

889 Parameters 

890 ---------- 

891 spec 

892 Maps header entry name to its location, data type description and 

893 optionally number of entries. See :py:attr:`Spec.basic` and 

894 :py:attr:`Spec.metadata`. 

895 char_encoding 

896 String character encoding 

897 

898 Returns 

899 ------- 

900 Dict mapping header entry name to its value 

901 """ 

902 

903 ret = {} 

904 # Decode each string from the numpy array read by np.fromfile 

905 decode = np.vectorize(lambda x: x.decode(char_encoding)) 

906 

907 for name, sp in spec.items(): 

908 self._file.seek(sp[0]) 

909 cnt = 1 if len(sp) < 3 else sp[2] 

910 v = np.fromfile(self._file, dtype=sp[1], count=cnt) 

911 if v.dtype.kind == "S" and name not in Spec.no_decode: 

912 # Silently ignore string decoding failures 

913 try: 

914 v = decode(v) 

915 except Exception: 

916 warnings.warn( 

917 f'Failed to decode "{name}" metadata ' 

918 "string. Check `char_encoding` parameter." 

919 ) 

920 

921 try: 

922 # For convenience, if the array contains only one single 

923 # entry, return this entry itself. 

924 v = v.item() 

925 except ValueError: 

926 v = np.squeeze(v) 

927 ret[name] = v 

928 return ret 

929 

930 

931def roi_array_to_dict(a: np.ndarray) -> List[Dict[str, List[int]]]: 

932 """Convert the `ROIs` structured arrays to :py:class:`dict` 

933 

934 Parameters 

935 ---------- 

936 a 

937 Structured array containing ROI data 

938 

939 Returns 

940 ------- 

941 One dict per ROI. Keys are "top_left", "bottom_right", and "bin", 

942 values are tuples whose first element is the x axis value and the 

943 second element is the y axis value. 

944 """ 

945 

946 dict_list = [] 

947 a = a[["startx", "starty", "endx", "endy", "groupx", "groupy"]] 

948 for sx, sy, ex, ey, gx, gy in a: 

949 roi_dict = { 

950 "top_left": [int(sx), int(sy)], 

951 "bottom_right": [int(ex), int(ey)], 

952 "bin": [int(gx), int(gy)], 

953 } 

954 dict_list.append(roi_dict) 

955 return dict_list