Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/io/_idl.py: 10%

431 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-22 06:44 +0000

1# IDLSave - a python module to read IDL 'save' files 

2# Copyright (c) 2010 Thomas P. Robitaille 

3 

4# Many thanks to Craig Markwardt for publishing the Unofficial Format 

5# Specification for IDL .sav files, without which this Python module would not 

6# exist (http://cow.physics.wisc.edu/~craigm/idl/savefmt). 

7 

8# This code was developed by with permission from ITT Visual Information 

9# Systems. IDL(r) is a registered trademark of ITT Visual Information Systems, 

10# Inc. for their Interactive Data Language software. 

11 

12# Permission is hereby granted, free of charge, to any person obtaining a 

13# copy of this software and associated documentation files (the "Software"), 

14# to deal in the Software without restriction, including without limitation 

15# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

16# and/or sell copies of the Software, and to permit persons to whom the 

17# Software is furnished to do so, subject to the following conditions: 

18 

19# The above copyright notice and this permission notice shall be included in 

20# all copies or substantial portions of the Software. 

21 

22# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

23# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

24# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

25# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

26# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 

27# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 

28# DEALINGS IN THE SOFTWARE. 

29 

30__all__ = ['readsav'] 

31 

32import struct 

33import numpy as np 

34import tempfile 

35import zlib 

36import warnings 

37 

38# Define the different data types that can be found in an IDL save file 

39DTYPE_DICT = {1: '>u1', 

40 2: '>i2', 

41 3: '>i4', 

42 4: '>f4', 

43 5: '>f8', 

44 6: '>c8', 

45 7: '|O', 

46 8: '|O', 

47 9: '>c16', 

48 10: '|O', 

49 11: '|O', 

50 12: '>u2', 

51 13: '>u4', 

52 14: '>i8', 

53 15: '>u8'} 

54 

55# Define the different record types that can be found in an IDL save file 

56RECTYPE_DICT = {0: "START_MARKER", 

57 1: "COMMON_VARIABLE", 

58 2: "VARIABLE", 

59 3: "SYSTEM_VARIABLE", 

60 6: "END_MARKER", 

61 10: "TIMESTAMP", 

62 12: "COMPILED", 

63 13: "IDENTIFICATION", 

64 14: "VERSION", 

65 15: "HEAP_HEADER", 

66 16: "HEAP_DATA", 

67 17: "PROMOTE64", 

68 19: "NOTICE", 

69 20: "DESCRIPTION"} 

70 

71# Define a dictionary to contain structure definitions 

72STRUCT_DICT = {} 

73 

74 

75def _align_32(f): 

76 '''Align to the next 32-bit position in a file''' 

77 

78 pos = f.tell() 

79 if pos % 4 != 0: 

80 f.seek(pos + 4 - pos % 4) 

81 return 

82 

83 

84def _skip_bytes(f, n): 

85 '''Skip `n` bytes''' 

86 f.read(n) 

87 return 

88 

89 

90def _read_bytes(f, n): 

91 '''Read the next `n` bytes''' 

92 return f.read(n) 

93 

94 

95def _read_byte(f): 

96 '''Read a single byte''' 

97 return np.uint8(struct.unpack('>B', f.read(4)[:1])[0]) 

98 

99 

100def _read_long(f): 

101 '''Read a signed 32-bit integer''' 

102 return np.int32(struct.unpack('>l', f.read(4))[0]) 

103 

104 

105def _read_int16(f): 

106 '''Read a signed 16-bit integer''' 

107 return np.int16(struct.unpack('>h', f.read(4)[2:4])[0]) 

108 

109 

110def _read_int32(f): 

111 '''Read a signed 32-bit integer''' 

112 return np.int32(struct.unpack('>i', f.read(4))[0]) 

113 

114 

115def _read_int64(f): 

116 '''Read a signed 64-bit integer''' 

117 return np.int64(struct.unpack('>q', f.read(8))[0]) 

118 

119 

120def _read_uint16(f): 

121 '''Read an unsigned 16-bit integer''' 

122 return np.uint16(struct.unpack('>H', f.read(4)[2:4])[0]) 

123 

124 

125def _read_uint32(f): 

126 '''Read an unsigned 32-bit integer''' 

127 return np.uint32(struct.unpack('>I', f.read(4))[0]) 

128 

129 

130def _read_uint64(f): 

131 '''Read an unsigned 64-bit integer''' 

132 return np.uint64(struct.unpack('>Q', f.read(8))[0]) 

133 

134 

135def _read_float32(f): 

136 '''Read a 32-bit float''' 

137 return np.float32(struct.unpack('>f', f.read(4))[0]) 

138 

139 

140def _read_float64(f): 

141 '''Read a 64-bit float''' 

142 return np.float64(struct.unpack('>d', f.read(8))[0]) 

143 

144 

145class Pointer: 

146 '''Class used to define pointers''' 

147 

148 def __init__(self, index): 

149 self.index = index 

150 return 

151 

152 

153class ObjectPointer(Pointer): 

154 '''Class used to define object pointers''' 

155 pass 

156 

157 

158def _read_string(f): 

159 '''Read a string''' 

160 length = _read_long(f) 

161 if length > 0: 

162 chars = _read_bytes(f, length).decode('latin1') 

163 _align_32(f) 

164 else: 

165 chars = '' 

166 return chars 

167 

168 

169def _read_string_data(f): 

170 '''Read a data string (length is specified twice)''' 

171 length = _read_long(f) 

172 if length > 0: 

173 length = _read_long(f) 

174 string_data = _read_bytes(f, length) 

175 _align_32(f) 

176 else: 

177 string_data = '' 

178 return string_data 

179 

180 

181def _read_data(f, dtype): 

182 '''Read a variable with a specified data type''' 

183 if dtype == 1: 

184 if _read_int32(f) != 1: 

185 raise Exception("Error occurred while reading byte variable") 

186 return _read_byte(f) 

187 elif dtype == 2: 

188 return _read_int16(f) 

189 elif dtype == 3: 

190 return _read_int32(f) 

191 elif dtype == 4: 

192 return _read_float32(f) 

193 elif dtype == 5: 

194 return _read_float64(f) 

195 elif dtype == 6: 

196 real = _read_float32(f) 

197 imag = _read_float32(f) 

198 return np.complex64(real + imag * 1j) 

199 elif dtype == 7: 

200 return _read_string_data(f) 

201 elif dtype == 8: 

202 raise Exception("Should not be here - please report this") 

203 elif dtype == 9: 

204 real = _read_float64(f) 

205 imag = _read_float64(f) 

206 return np.complex128(real + imag * 1j) 

207 elif dtype == 10: 

208 return Pointer(_read_int32(f)) 

209 elif dtype == 11: 

210 return ObjectPointer(_read_int32(f)) 

211 elif dtype == 12: 

212 return _read_uint16(f) 

213 elif dtype == 13: 

214 return _read_uint32(f) 

215 elif dtype == 14: 

216 return _read_int64(f) 

217 elif dtype == 15: 

218 return _read_uint64(f) 

219 else: 

220 raise Exception("Unknown IDL type: %i - please report this" % dtype) 

221 

222 

223def _read_structure(f, array_desc, struct_desc): 

224 ''' 

225 Read a structure, with the array and structure descriptors given as 

226 `array_desc` and `structure_desc` respectively. 

227 ''' 

228 

229 nrows = array_desc['nelements'] 

230 columns = struct_desc['tagtable'] 

231 

232 dtype = [] 

233 for col in columns: 

234 if col['structure'] or col['array']: 

235 dtype.append(((col['name'].lower(), col['name']), np.object_)) 

236 else: 

237 if col['typecode'] in DTYPE_DICT: 

238 dtype.append(((col['name'].lower(), col['name']), 

239 DTYPE_DICT[col['typecode']])) 

240 else: 

241 raise Exception("Variable type %i not implemented" % 

242 col['typecode']) 

243 

244 structure = np.rec.recarray((nrows, ), dtype=dtype) 

245 

246 for i in range(nrows): 

247 for col in columns: 

248 dtype = col['typecode'] 

249 if col['structure']: 

250 structure[col['name']][i] = _read_structure(f, 

251 struct_desc['arrtable'][col['name']], 

252 struct_desc['structtable'][col['name']]) 

253 elif col['array']: 

254 structure[col['name']][i] = _read_array(f, dtype, 

255 struct_desc['arrtable'][col['name']]) 

256 else: 

257 structure[col['name']][i] = _read_data(f, dtype) 

258 

259 # Reshape structure if needed 

260 if array_desc['ndims'] > 1: 

261 dims = array_desc['dims'][:int(array_desc['ndims'])] 

262 dims.reverse() 

263 structure = structure.reshape(dims) 

264 

265 return structure 

266 

267 

268def _read_array(f, typecode, array_desc): 

269 ''' 

270 Read an array of type `typecode`, with the array descriptor given as 

271 `array_desc`. 

272 ''' 

273 

274 if typecode in [1, 3, 4, 5, 6, 9, 13, 14, 15]: 

275 

276 if typecode == 1: 

277 nbytes = _read_int32(f) 

278 if nbytes != array_desc['nbytes']: 

279 warnings.warn("Not able to verify number of bytes from header", 

280 stacklevel=3) 

281 

282 # Read bytes as numpy array 

283 array = np.frombuffer(f.read(array_desc['nbytes']), 

284 dtype=DTYPE_DICT[typecode]) 

285 

286 elif typecode in [2, 12]: 

287 

288 # These are 2 byte types, need to skip every two as they are not packed 

289 

290 array = np.frombuffer(f.read(array_desc['nbytes']*2), 

291 dtype=DTYPE_DICT[typecode])[1::2] 

292 

293 else: 

294 

295 # Read bytes into list 

296 array = [] 

297 for i in range(array_desc['nelements']): 

298 dtype = typecode 

299 data = _read_data(f, dtype) 

300 array.append(data) 

301 

302 array = np.array(array, dtype=np.object_) 

303 

304 # Reshape array if needed 

305 if array_desc['ndims'] > 1: 

306 dims = array_desc['dims'][:int(array_desc['ndims'])] 

307 dims.reverse() 

308 array = array.reshape(dims) 

309 

310 # Go to next alignment position 

311 _align_32(f) 

312 

313 return array 

314 

315 

316def _read_record(f): 

317 '''Function to read in a full record''' 

318 

319 record = {'rectype': _read_long(f)} 

320 

321 nextrec = _read_uint32(f) 

322 nextrec += _read_uint32(f).astype(np.int64) * 2**32 

323 

324 _skip_bytes(f, 4) 

325 

326 if record['rectype'] not in RECTYPE_DICT: 

327 raise Exception("Unknown RECTYPE: %i" % record['rectype']) 

328 

329 record['rectype'] = RECTYPE_DICT[record['rectype']] 

330 

331 if record['rectype'] in ["VARIABLE", "HEAP_DATA"]: 

332 

333 if record['rectype'] == "VARIABLE": 

334 record['varname'] = _read_string(f) 

335 else: 

336 record['heap_index'] = _read_long(f) 

337 _skip_bytes(f, 4) 

338 

339 rectypedesc = _read_typedesc(f) 

340 

341 if rectypedesc['typecode'] == 0: 

342 

343 if nextrec == f.tell(): 

344 record['data'] = None # Indicates NULL value 

345 else: 

346 raise ValueError("Unexpected type code: 0") 

347 

348 else: 

349 

350 varstart = _read_long(f) 

351 if varstart != 7: 

352 raise Exception("VARSTART is not 7") 

353 

354 if rectypedesc['structure']: 

355 record['data'] = _read_structure(f, rectypedesc['array_desc'], 

356 rectypedesc['struct_desc']) 

357 elif rectypedesc['array']: 

358 record['data'] = _read_array(f, rectypedesc['typecode'], 

359 rectypedesc['array_desc']) 

360 else: 

361 dtype = rectypedesc['typecode'] 

362 record['data'] = _read_data(f, dtype) 

363 

364 elif record['rectype'] == "TIMESTAMP": 

365 

366 _skip_bytes(f, 4*256) 

367 record['date'] = _read_string(f) 

368 record['user'] = _read_string(f) 

369 record['host'] = _read_string(f) 

370 

371 elif record['rectype'] == "VERSION": 

372 

373 record['format'] = _read_long(f) 

374 record['arch'] = _read_string(f) 

375 record['os'] = _read_string(f) 

376 record['release'] = _read_string(f) 

377 

378 elif record['rectype'] == "IDENTIFICATON": 

379 

380 record['author'] = _read_string(f) 

381 record['title'] = _read_string(f) 

382 record['idcode'] = _read_string(f) 

383 

384 elif record['rectype'] == "NOTICE": 

385 

386 record['notice'] = _read_string(f) 

387 

388 elif record['rectype'] == "DESCRIPTION": 

389 

390 record['description'] = _read_string_data(f) 

391 

392 elif record['rectype'] == "HEAP_HEADER": 

393 

394 record['nvalues'] = _read_long(f) 

395 record['indices'] = [_read_long(f) for _ in range(record['nvalues'])] 

396 

397 elif record['rectype'] == "COMMONBLOCK": 

398 

399 record['nvars'] = _read_long(f) 

400 record['name'] = _read_string(f) 

401 record['varnames'] = [_read_string(f) for _ in range(record['nvars'])] 

402 

403 elif record['rectype'] == "END_MARKER": 

404 

405 record['end'] = True 

406 

407 elif record['rectype'] == "UNKNOWN": 

408 

409 warnings.warn("Skipping UNKNOWN record", stacklevel=3) 

410 

411 elif record['rectype'] == "SYSTEM_VARIABLE": 

412 

413 warnings.warn("Skipping SYSTEM_VARIABLE record", stacklevel=3) 

414 

415 else: 

416 

417 raise Exception(f"record['rectype']={record['rectype']} not implemented") 

418 

419 f.seek(nextrec) 

420 

421 return record 

422 

423 

424def _read_typedesc(f): 

425 '''Function to read in a type descriptor''' 

426 

427 typedesc = {'typecode': _read_long(f), 'varflags': _read_long(f)} 

428 

429 if typedesc['varflags'] & 2 == 2: 

430 raise Exception("System variables not implemented") 

431 

432 typedesc['array'] = typedesc['varflags'] & 4 == 4 

433 typedesc['structure'] = typedesc['varflags'] & 32 == 32 

434 

435 if typedesc['structure']: 

436 typedesc['array_desc'] = _read_arraydesc(f) 

437 typedesc['struct_desc'] = _read_structdesc(f) 

438 elif typedesc['array']: 

439 typedesc['array_desc'] = _read_arraydesc(f) 

440 

441 return typedesc 

442 

443 

444def _read_arraydesc(f): 

445 '''Function to read in an array descriptor''' 

446 

447 arraydesc = {'arrstart': _read_long(f)} 

448 

449 if arraydesc['arrstart'] == 8: 

450 

451 _skip_bytes(f, 4) 

452 

453 arraydesc['nbytes'] = _read_long(f) 

454 arraydesc['nelements'] = _read_long(f) 

455 arraydesc['ndims'] = _read_long(f) 

456 

457 _skip_bytes(f, 8) 

458 

459 arraydesc['nmax'] = _read_long(f) 

460 

461 arraydesc['dims'] = [_read_long(f) for _ in range(arraydesc['nmax'])] 

462 

463 elif arraydesc['arrstart'] == 18: 

464 

465 warnings.warn("Using experimental 64-bit array read", stacklevel=3) 

466 

467 _skip_bytes(f, 8) 

468 

469 arraydesc['nbytes'] = _read_uint64(f) 

470 arraydesc['nelements'] = _read_uint64(f) 

471 arraydesc['ndims'] = _read_long(f) 

472 

473 _skip_bytes(f, 8) 

474 

475 arraydesc['nmax'] = 8 

476 

477 arraydesc['dims'] = [] 

478 for d in range(arraydesc['nmax']): 

479 v = _read_long(f) 

480 if v != 0: 

481 raise Exception("Expected a zero in ARRAY_DESC") 

482 arraydesc['dims'].append(_read_long(f)) 

483 

484 else: 

485 

486 raise Exception("Unknown ARRSTART: %i" % arraydesc['arrstart']) 

487 

488 return arraydesc 

489 

490 

491def _read_structdesc(f): 

492 '''Function to read in a structure descriptor''' 

493 

494 structdesc = {} 

495 

496 structstart = _read_long(f) 

497 if structstart != 9: 

498 raise Exception("STRUCTSTART should be 9") 

499 

500 structdesc['name'] = _read_string(f) 

501 predef = _read_long(f) 

502 structdesc['ntags'] = _read_long(f) 

503 structdesc['nbytes'] = _read_long(f) 

504 

505 structdesc['predef'] = predef & 1 

506 structdesc['inherits'] = predef & 2 

507 structdesc['is_super'] = predef & 4 

508 

509 if not structdesc['predef']: 

510 

511 structdesc['tagtable'] = [_read_tagdesc(f) 

512 for _ in range(structdesc['ntags'])] 

513 

514 for tag in structdesc['tagtable']: 

515 tag['name'] = _read_string(f) 

516 

517 structdesc['arrtable'] = {tag['name']: _read_arraydesc(f) 

518 for tag in structdesc['tagtable'] 

519 if tag['array']} 

520 

521 structdesc['structtable'] = {tag['name']: _read_structdesc(f) 

522 for tag in structdesc['tagtable'] 

523 if tag['structure']} 

524 

525 if structdesc['inherits'] or structdesc['is_super']: 

526 structdesc['classname'] = _read_string(f) 

527 structdesc['nsupclasses'] = _read_long(f) 

528 structdesc['supclassnames'] = [ 

529 _read_string(f) for _ in range(structdesc['nsupclasses'])] 

530 structdesc['supclasstable'] = [ 

531 _read_structdesc(f) for _ in range(structdesc['nsupclasses'])] 

532 

533 STRUCT_DICT[structdesc['name']] = structdesc 

534 

535 else: 

536 

537 if structdesc['name'] not in STRUCT_DICT: 

538 raise Exception("PREDEF=1 but can't find definition") 

539 

540 structdesc = STRUCT_DICT[structdesc['name']] 

541 

542 return structdesc 

543 

544 

545def _read_tagdesc(f): 

546 '''Function to read in a tag descriptor''' 

547 

548 tagdesc = {'offset': _read_long(f)} 

549 

550 if tagdesc['offset'] == -1: 

551 tagdesc['offset'] = _read_uint64(f) 

552 

553 tagdesc['typecode'] = _read_long(f) 

554 tagflags = _read_long(f) 

555 

556 tagdesc['array'] = tagflags & 4 == 4 

557 tagdesc['structure'] = tagflags & 32 == 32 

558 tagdesc['scalar'] = tagdesc['typecode'] in DTYPE_DICT 

559 # Assume '10'x is scalar 

560 

561 return tagdesc 

562 

563 

564def _replace_heap(variable, heap): 

565 

566 if isinstance(variable, Pointer): 

567 

568 while isinstance(variable, Pointer): 

569 

570 if variable.index == 0: 

571 variable = None 

572 else: 

573 if variable.index in heap: 

574 variable = heap[variable.index] 

575 else: 

576 warnings.warn("Variable referenced by pointer not found " 

577 "in heap: variable will be set to None", 

578 stacklevel=3) 

579 variable = None 

580 

581 replace, new = _replace_heap(variable, heap) 

582 

583 if replace: 

584 variable = new 

585 

586 return True, variable 

587 

588 elif isinstance(variable, np.rec.recarray): 

589 

590 # Loop over records 

591 for ir, record in enumerate(variable): 

592 

593 replace, new = _replace_heap(record, heap) 

594 

595 if replace: 

596 variable[ir] = new 

597 

598 return False, variable 

599 

600 elif isinstance(variable, np.record): 

601 

602 # Loop over values 

603 for iv, value in enumerate(variable): 

604 

605 replace, new = _replace_heap(value, heap) 

606 

607 if replace: 

608 variable[iv] = new 

609 

610 return False, variable 

611 

612 elif isinstance(variable, np.ndarray): 

613 

614 # Loop over values if type is np.object_ 

615 if variable.dtype.type is np.object_: 

616 

617 for iv in range(variable.size): 

618 

619 replace, new = _replace_heap(variable.item(iv), heap) 

620 

621 if replace: 

622 variable.reshape(-1)[iv] = new 

623 

624 return False, variable 

625 

626 else: 

627 

628 return False, variable 

629 

630 

631class AttrDict(dict): 

632 ''' 

633 A case-insensitive dictionary with access via item, attribute, and call 

634 notations: 

635 

636 >>> from scipy.io._idl import AttrDict 

637 >>> d = AttrDict() 

638 >>> d['Variable'] = 123 

639 >>> d['Variable'] 

640 123 

641 >>> d.Variable 

642 123 

643 >>> d.variable 

644 123 

645 >>> d('VARIABLE') 

646 123 

647 >>> d['missing'] 

648 Traceback (most recent error last): 

649 ... 

650 KeyError: 'missing' 

651 >>> d.missing 

652 Traceback (most recent error last): 

653 ... 

654 AttributeError: 'AttrDict' object has no attribute 'missing' 

655 ''' 

656 

657 def __init__(self, init={}): 

658 dict.__init__(self, init) 

659 

660 def __getitem__(self, name): 

661 return super().__getitem__(name.lower()) 

662 

663 def __setitem__(self, key, value): 

664 return super().__setitem__(key.lower(), value) 

665 

666 def __getattr__(self, name): 

667 try: 

668 return self.__getitem__(name) 

669 except KeyError: 

670 raise AttributeError( 

671 f"'{type(self)}' object has no attribute '{name}'") from None 

672 

673 __setattr__ = __setitem__ 

674 __call__ = __getitem__ 

675 

676 

677def readsav(file_name, idict=None, python_dict=False, 

678 uncompressed_file_name=None, verbose=False): 

679 """ 

680 Read an IDL .sav file. 

681 

682 Parameters 

683 ---------- 

684 file_name : str 

685 Name of the IDL save file. 

686 idict : dict, optional 

687 Dictionary in which to insert .sav file variables. 

688 python_dict : bool, optional 

689 By default, the object return is not a Python dictionary, but a 

690 case-insensitive dictionary with item, attribute, and call access 

691 to variables. To get a standard Python dictionary, set this option 

692 to True. 

693 uncompressed_file_name : str, optional 

694 This option only has an effect for .sav files written with the 

695 /compress option. If a file name is specified, compressed .sav 

696 files are uncompressed to this file. Otherwise, readsav will use 

697 the `tempfile` module to determine a temporary filename 

698 automatically, and will remove the temporary file upon successfully 

699 reading it in. 

700 verbose : bool, optional 

701 Whether to print out information about the save file, including 

702 the records read, and available variables. 

703 

704 Returns 

705 ------- 

706 idl_dict : AttrDict or dict 

707 If `python_dict` is set to False (default), this function returns a 

708 case-insensitive dictionary with item, attribute, and call access 

709 to variables. If `python_dict` is set to True, this function 

710 returns a Python dictionary with all variable names in lowercase. 

711 If `idict` was specified, then variables are written to the 

712 dictionary specified, and the updated dictionary is returned. 

713 

714 Examples 

715 -------- 

716 >>> from os.path import dirname, join as pjoin 

717 >>> import scipy.io as sio 

718 >>> from scipy.io import readsav 

719 

720 Get the filename for an example .sav file from the tests/data directory. 

721 

722 >>> data_dir = pjoin(dirname(sio.__file__), 'tests', 'data') 

723 >>> sav_fname = pjoin(data_dir, 'array_float32_1d.sav') 

724 

725 Load the .sav file contents. 

726 

727 >>> sav_data = readsav(sav_fname) 

728 

729 Get keys of the .sav file contents. 

730 

731 >>> print(sav_data.keys()) 

732 dict_keys(['array1d']) 

733 

734 Access a content with a key. 

735 

736 >>> print(sav_data['array1d']) 

737 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

738 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

739 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

740 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

741 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 

742 0. 0. 0.] 

743 

744 """ 

745 

746 # Initialize record and variable holders 

747 records = [] 

748 if python_dict or idict: 

749 variables = {} 

750 else: 

751 variables = AttrDict() 

752 

753 # Open the IDL file 

754 f = open(file_name, 'rb') 

755 

756 # Read the signature, which should be 'SR' 

757 signature = _read_bytes(f, 2) 

758 if signature != b'SR': 

759 raise Exception("Invalid SIGNATURE: %s" % signature) 

760 

761 # Next, the record format, which is '\x00\x04' for normal .sav 

762 # files, and '\x00\x06' for compressed .sav files. 

763 recfmt = _read_bytes(f, 2) 

764 

765 if recfmt == b'\x00\x04': 

766 pass 

767 

768 elif recfmt == b'\x00\x06': 

769 

770 if verbose: 

771 print("IDL Save file is compressed") 

772 

773 if uncompressed_file_name: 

774 fout = open(uncompressed_file_name, 'w+b') 

775 else: 

776 fout = tempfile.NamedTemporaryFile(suffix='.sav') 

777 

778 if verbose: 

779 print(" -> expanding to %s" % fout.name) 

780 

781 # Write header 

782 fout.write(b'SR\x00\x04') 

783 

784 # Cycle through records 

785 while True: 

786 

787 # Read record type 

788 rectype = _read_long(f) 

789 fout.write(struct.pack('>l', int(rectype))) 

790 

791 # Read position of next record and return as int 

792 nextrec = _read_uint32(f) 

793 nextrec += _read_uint32(f).astype(np.int64) * 2**32 

794 

795 # Read the unknown 4 bytes 

796 unknown = f.read(4) 

797 

798 # Check if the end of the file has been reached 

799 if RECTYPE_DICT[rectype] == 'END_MARKER': 

800 modval = np.int64(2**32) 

801 fout.write(struct.pack('>I', int(nextrec) % modval)) 

802 fout.write( 

803 struct.pack('>I', int((nextrec - (nextrec % modval)) / modval)) 

804 ) 

805 fout.write(unknown) 

806 break 

807 

808 # Find current position 

809 pos = f.tell() 

810 

811 # Decompress record 

812 rec_string = zlib.decompress(f.read(nextrec-pos)) 

813 

814 # Find new position of next record 

815 nextrec = fout.tell() + len(rec_string) + 12 

816 

817 # Write out record 

818 fout.write(struct.pack('>I', int(nextrec % 2**32))) 

819 fout.write(struct.pack('>I', int((nextrec - (nextrec % 2**32)) / 2**32))) 

820 fout.write(unknown) 

821 fout.write(rec_string) 

822 

823 # Close the original compressed file 

824 f.close() 

825 

826 # Set f to be the decompressed file, and skip the first four bytes 

827 f = fout 

828 f.seek(4) 

829 

830 else: 

831 raise Exception("Invalid RECFMT: %s" % recfmt) 

832 

833 # Loop through records, and add them to the list 

834 while True: 

835 r = _read_record(f) 

836 records.append(r) 

837 if 'end' in r: 

838 if r['end']: 

839 break 

840 

841 # Close the file 

842 f.close() 

843 

844 # Find heap data variables 

845 heap = {} 

846 for r in records: 

847 if r['rectype'] == "HEAP_DATA": 

848 heap[r['heap_index']] = r['data'] 

849 

850 # Find all variables 

851 for r in records: 

852 if r['rectype'] == "VARIABLE": 

853 replace, new = _replace_heap(r['data'], heap) 

854 if replace: 

855 r['data'] = new 

856 variables[r['varname'].lower()] = r['data'] 

857 

858 if verbose: 

859 

860 # Print out timestamp info about the file 

861 for record in records: 

862 if record['rectype'] == "TIMESTAMP": 

863 print("-"*50) 

864 print("Date: %s" % record['date']) 

865 print("User: %s" % record['user']) 

866 print("Host: %s" % record['host']) 

867 break 

868 

869 # Print out version info about the file 

870 for record in records: 

871 if record['rectype'] == "VERSION": 

872 print("-"*50) 

873 print("Format: %s" % record['format']) 

874 print("Architecture: %s" % record['arch']) 

875 print("Operating System: %s" % record['os']) 

876 print("IDL Version: %s" % record['release']) 

877 break 

878 

879 # Print out identification info about the file 

880 for record in records: 

881 if record['rectype'] == "IDENTIFICATON": 

882 print("-"*50) 

883 print("Author: %s" % record['author']) 

884 print("Title: %s" % record['title']) 

885 print("ID Code: %s" % record['idcode']) 

886 break 

887 

888 # Print out descriptions saved with the file 

889 for record in records: 

890 if record['rectype'] == "DESCRIPTION": 

891 print("-"*50) 

892 print("Description: %s" % record['description']) 

893 break 

894 

895 print("-"*50) 

896 print("Successfully read %i records of which:" % 

897 (len(records))) 

898 

899 # Create convenience list of record types 

900 rectypes = [r['rectype'] for r in records] 

901 

902 for rt in set(rectypes): 

903 if rt != 'END_MARKER': 

904 print(" - %i are of type %s" % (rectypes.count(rt), rt)) 

905 print("-"*50) 

906 

907 if 'VARIABLE' in rectypes: 

908 print("Available variables:") 

909 for var in variables: 

910 print(f" - {var} [{type(variables[var])}]") 

911 print("-"*50) 

912 

913 if idict: 

914 for var in variables: 

915 idict[var] = variables[var] 

916 return idict 

917 else: 

918 return variables