_idl.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. # IDLSave - a python module to read IDL 'save' files
  2. # Copyright (c) 2010 Thomas P. Robitaille
  3. # Many thanks to Craig Markwardt for publishing the Unofficial Format
  4. # Specification for IDL .sav files, without which this Python module would not
  5. # exist (http://cow.physics.wisc.edu/~craigm/idl/savefmt).
  6. # This code was developed by with permission from ITT Visual Information
  7. # Systems. IDL(r) is a registered trademark of ITT Visual Information Systems,
  8. # Inc. for their Interactive Data Language software.
  9. # Permission is hereby granted, free of charge, to any person obtaining a
  10. # copy of this software and associated documentation files (the "Software"),
  11. # to deal in the Software without restriction, including without limitation
  12. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  13. # and/or sell copies of the Software, and to permit persons to whom the
  14. # Software is furnished to do so, subject to the following conditions:
  15. # The above copyright notice and this permission notice shall be included in
  16. # all copies or substantial portions of the Software.
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. # DEALINGS IN THE SOFTWARE.
  24. __all__ = ['readsav']
  25. import struct
  26. import numpy as np
  27. import tempfile
  28. import zlib
  29. import warnings
  30. # Define the different data types that can be found in an IDL save file
  31. DTYPE_DICT = {1: '>u1',
  32. 2: '>i2',
  33. 3: '>i4',
  34. 4: '>f4',
  35. 5: '>f8',
  36. 6: '>c8',
  37. 7: '|O',
  38. 8: '|O',
  39. 9: '>c16',
  40. 10: '|O',
  41. 11: '|O',
  42. 12: '>u2',
  43. 13: '>u4',
  44. 14: '>i8',
  45. 15: '>u8'}
  46. # Define the different record types that can be found in an IDL save file
  47. RECTYPE_DICT = {0: "START_MARKER",
  48. 1: "COMMON_VARIABLE",
  49. 2: "VARIABLE",
  50. 3: "SYSTEM_VARIABLE",
  51. 6: "END_MARKER",
  52. 10: "TIMESTAMP",
  53. 12: "COMPILED",
  54. 13: "IDENTIFICATION",
  55. 14: "VERSION",
  56. 15: "HEAP_HEADER",
  57. 16: "HEAP_DATA",
  58. 17: "PROMOTE64",
  59. 19: "NOTICE",
  60. 20: "DESCRIPTION"}
  61. # Define a dictionary to contain structure definitions
  62. STRUCT_DICT = {}
  63. def _align_32(f):
  64. '''Align to the next 32-bit position in a file'''
  65. pos = f.tell()
  66. if pos % 4 != 0:
  67. f.seek(pos + 4 - pos % 4)
  68. return
  69. def _skip_bytes(f, n):
  70. '''Skip `n` bytes'''
  71. f.read(n)
  72. return
  73. def _read_bytes(f, n):
  74. '''Read the next `n` bytes'''
  75. return f.read(n)
  76. def _read_byte(f):
  77. '''Read a single byte'''
  78. return np.uint8(struct.unpack('>B', f.read(4)[:1])[0])
  79. def _read_long(f):
  80. '''Read a signed 32-bit integer'''
  81. return np.int32(struct.unpack('>l', f.read(4))[0])
  82. def _read_int16(f):
  83. '''Read a signed 16-bit integer'''
  84. return np.int16(struct.unpack('>h', f.read(4)[2:4])[0])
  85. def _read_int32(f):
  86. '''Read a signed 32-bit integer'''
  87. return np.int32(struct.unpack('>i', f.read(4))[0])
  88. def _read_int64(f):
  89. '''Read a signed 64-bit integer'''
  90. return np.int64(struct.unpack('>q', f.read(8))[0])
  91. def _read_uint16(f):
  92. '''Read an unsigned 16-bit integer'''
  93. return np.uint16(struct.unpack('>H', f.read(4)[2:4])[0])
  94. def _read_uint32(f):
  95. '''Read an unsigned 32-bit integer'''
  96. return np.uint32(struct.unpack('>I', f.read(4))[0])
  97. def _read_uint64(f):
  98. '''Read an unsigned 64-bit integer'''
  99. return np.uint64(struct.unpack('>Q', f.read(8))[0])
  100. def _read_float32(f):
  101. '''Read a 32-bit float'''
  102. return np.float32(struct.unpack('>f', f.read(4))[0])
  103. def _read_float64(f):
  104. '''Read a 64-bit float'''
  105. return np.float64(struct.unpack('>d', f.read(8))[0])
  106. class Pointer:
  107. '''Class used to define pointers'''
  108. def __init__(self, index):
  109. self.index = index
  110. return
  111. class ObjectPointer(Pointer):
  112. '''Class used to define object pointers'''
  113. pass
  114. def _read_string(f):
  115. '''Read a string'''
  116. length = _read_long(f)
  117. if length > 0:
  118. chars = _read_bytes(f, length).decode('latin1')
  119. _align_32(f)
  120. else:
  121. chars = ''
  122. return chars
  123. def _read_string_data(f):
  124. '''Read a data string (length is specified twice)'''
  125. length = _read_long(f)
  126. if length > 0:
  127. length = _read_long(f)
  128. string_data = _read_bytes(f, length)
  129. _align_32(f)
  130. else:
  131. string_data = ''
  132. return string_data
  133. def _read_data(f, dtype):
  134. '''Read a variable with a specified data type'''
  135. if dtype == 1:
  136. if _read_int32(f) != 1:
  137. raise Exception("Error occurred while reading byte variable")
  138. return _read_byte(f)
  139. elif dtype == 2:
  140. return _read_int16(f)
  141. elif dtype == 3:
  142. return _read_int32(f)
  143. elif dtype == 4:
  144. return _read_float32(f)
  145. elif dtype == 5:
  146. return _read_float64(f)
  147. elif dtype == 6:
  148. real = _read_float32(f)
  149. imag = _read_float32(f)
  150. return np.complex64(real + imag * 1j)
  151. elif dtype == 7:
  152. return _read_string_data(f)
  153. elif dtype == 8:
  154. raise Exception("Should not be here - please report this")
  155. elif dtype == 9:
  156. real = _read_float64(f)
  157. imag = _read_float64(f)
  158. return np.complex128(real + imag * 1j)
  159. elif dtype == 10:
  160. return Pointer(_read_int32(f))
  161. elif dtype == 11:
  162. return ObjectPointer(_read_int32(f))
  163. elif dtype == 12:
  164. return _read_uint16(f)
  165. elif dtype == 13:
  166. return _read_uint32(f)
  167. elif dtype == 14:
  168. return _read_int64(f)
  169. elif dtype == 15:
  170. return _read_uint64(f)
  171. else:
  172. raise Exception("Unknown IDL type: %i - please report this" % dtype)
  173. def _read_structure(f, array_desc, struct_desc):
  174. '''
  175. Read a structure, with the array and structure descriptors given as
  176. `array_desc` and `structure_desc` respectively.
  177. '''
  178. nrows = array_desc['nelements']
  179. columns = struct_desc['tagtable']
  180. dtype = []
  181. for col in columns:
  182. if col['structure'] or col['array']:
  183. dtype.append(((col['name'].lower(), col['name']), np.object_))
  184. else:
  185. if col['typecode'] in DTYPE_DICT:
  186. dtype.append(((col['name'].lower(), col['name']),
  187. DTYPE_DICT[col['typecode']]))
  188. else:
  189. raise Exception("Variable type %i not implemented" %
  190. col['typecode'])
  191. structure = np.recarray((nrows, ), dtype=dtype)
  192. for i in range(nrows):
  193. for col in columns:
  194. dtype = col['typecode']
  195. if col['structure']:
  196. structure[col['name']][i] = _read_structure(f,
  197. struct_desc['arrtable'][col['name']],
  198. struct_desc['structtable'][col['name']])
  199. elif col['array']:
  200. structure[col['name']][i] = _read_array(f, dtype,
  201. struct_desc['arrtable'][col['name']])
  202. else:
  203. structure[col['name']][i] = _read_data(f, dtype)
  204. # Reshape structure if needed
  205. if array_desc['ndims'] > 1:
  206. dims = array_desc['dims'][:int(array_desc['ndims'])]
  207. dims.reverse()
  208. structure = structure.reshape(dims)
  209. return structure
  210. def _read_array(f, typecode, array_desc):
  211. '''
  212. Read an array of type `typecode`, with the array descriptor given as
  213. `array_desc`.
  214. '''
  215. if typecode in [1, 3, 4, 5, 6, 9, 13, 14, 15]:
  216. if typecode == 1:
  217. nbytes = _read_int32(f)
  218. if nbytes != array_desc['nbytes']:
  219. warnings.warn("Not able to verify number of bytes from header")
  220. # Read bytes as numpy array
  221. array = np.frombuffer(f.read(array_desc['nbytes']),
  222. dtype=DTYPE_DICT[typecode])
  223. elif typecode in [2, 12]:
  224. # These are 2 byte types, need to skip every two as they are not packed
  225. array = np.frombuffer(f.read(array_desc['nbytes']*2),
  226. dtype=DTYPE_DICT[typecode])[1::2]
  227. else:
  228. # Read bytes into list
  229. array = []
  230. for i in range(array_desc['nelements']):
  231. dtype = typecode
  232. data = _read_data(f, dtype)
  233. array.append(data)
  234. array = np.array(array, dtype=np.object_)
  235. # Reshape array if needed
  236. if array_desc['ndims'] > 1:
  237. dims = array_desc['dims'][:int(array_desc['ndims'])]
  238. dims.reverse()
  239. array = array.reshape(dims)
  240. # Go to next alignment position
  241. _align_32(f)
  242. return array
  243. def _read_record(f):
  244. '''Function to read in a full record'''
  245. record = {'rectype': _read_long(f)}
  246. nextrec = _read_uint32(f)
  247. nextrec += _read_uint32(f) * 2**32
  248. _skip_bytes(f, 4)
  249. if not record['rectype'] in RECTYPE_DICT:
  250. raise Exception("Unknown RECTYPE: %i" % record['rectype'])
  251. record['rectype'] = RECTYPE_DICT[record['rectype']]
  252. if record['rectype'] in ["VARIABLE", "HEAP_DATA"]:
  253. if record['rectype'] == "VARIABLE":
  254. record['varname'] = _read_string(f)
  255. else:
  256. record['heap_index'] = _read_long(f)
  257. _skip_bytes(f, 4)
  258. rectypedesc = _read_typedesc(f)
  259. if rectypedesc['typecode'] == 0:
  260. if nextrec == f.tell():
  261. record['data'] = None # Indicates NULL value
  262. else:
  263. raise ValueError("Unexpected type code: 0")
  264. else:
  265. varstart = _read_long(f)
  266. if varstart != 7:
  267. raise Exception("VARSTART is not 7")
  268. if rectypedesc['structure']:
  269. record['data'] = _read_structure(f, rectypedesc['array_desc'],
  270. rectypedesc['struct_desc'])
  271. elif rectypedesc['array']:
  272. record['data'] = _read_array(f, rectypedesc['typecode'],
  273. rectypedesc['array_desc'])
  274. else:
  275. dtype = rectypedesc['typecode']
  276. record['data'] = _read_data(f, dtype)
  277. elif record['rectype'] == "TIMESTAMP":
  278. _skip_bytes(f, 4*256)
  279. record['date'] = _read_string(f)
  280. record['user'] = _read_string(f)
  281. record['host'] = _read_string(f)
  282. elif record['rectype'] == "VERSION":
  283. record['format'] = _read_long(f)
  284. record['arch'] = _read_string(f)
  285. record['os'] = _read_string(f)
  286. record['release'] = _read_string(f)
  287. elif record['rectype'] == "IDENTIFICATON":
  288. record['author'] = _read_string(f)
  289. record['title'] = _read_string(f)
  290. record['idcode'] = _read_string(f)
  291. elif record['rectype'] == "NOTICE":
  292. record['notice'] = _read_string(f)
  293. elif record['rectype'] == "DESCRIPTION":
  294. record['description'] = _read_string_data(f)
  295. elif record['rectype'] == "HEAP_HEADER":
  296. record['nvalues'] = _read_long(f)
  297. record['indices'] = [_read_long(f) for _ in range(record['nvalues'])]
  298. elif record['rectype'] == "COMMONBLOCK":
  299. record['nvars'] = _read_long(f)
  300. record['name'] = _read_string(f)
  301. record['varnames'] = [_read_string(f) for _ in range(record['nvars'])]
  302. elif record['rectype'] == "END_MARKER":
  303. record['end'] = True
  304. elif record['rectype'] == "UNKNOWN":
  305. warnings.warn("Skipping UNKNOWN record")
  306. elif record['rectype'] == "SYSTEM_VARIABLE":
  307. warnings.warn("Skipping SYSTEM_VARIABLE record")
  308. else:
  309. raise Exception("record['rectype']=%s not implemented" %
  310. record['rectype'])
  311. f.seek(nextrec)
  312. return record
  313. def _read_typedesc(f):
  314. '''Function to read in a type descriptor'''
  315. typedesc = {'typecode': _read_long(f), 'varflags': _read_long(f)}
  316. if typedesc['varflags'] & 2 == 2:
  317. raise Exception("System variables not implemented")
  318. typedesc['array'] = typedesc['varflags'] & 4 == 4
  319. typedesc['structure'] = typedesc['varflags'] & 32 == 32
  320. if typedesc['structure']:
  321. typedesc['array_desc'] = _read_arraydesc(f)
  322. typedesc['struct_desc'] = _read_structdesc(f)
  323. elif typedesc['array']:
  324. typedesc['array_desc'] = _read_arraydesc(f)
  325. return typedesc
  326. def _read_arraydesc(f):
  327. '''Function to read in an array descriptor'''
  328. arraydesc = {'arrstart': _read_long(f)}
  329. if arraydesc['arrstart'] == 8:
  330. _skip_bytes(f, 4)
  331. arraydesc['nbytes'] = _read_long(f)
  332. arraydesc['nelements'] = _read_long(f)
  333. arraydesc['ndims'] = _read_long(f)
  334. _skip_bytes(f, 8)
  335. arraydesc['nmax'] = _read_long(f)
  336. arraydesc['dims'] = [_read_long(f) for _ in range(arraydesc['nmax'])]
  337. elif arraydesc['arrstart'] == 18:
  338. warnings.warn("Using experimental 64-bit array read")
  339. _skip_bytes(f, 8)
  340. arraydesc['nbytes'] = _read_uint64(f)
  341. arraydesc['nelements'] = _read_uint64(f)
  342. arraydesc['ndims'] = _read_long(f)
  343. _skip_bytes(f, 8)
  344. arraydesc['nmax'] = 8
  345. arraydesc['dims'] = []
  346. for d in range(arraydesc['nmax']):
  347. v = _read_long(f)
  348. if v != 0:
  349. raise Exception("Expected a zero in ARRAY_DESC")
  350. arraydesc['dims'].append(_read_long(f))
  351. else:
  352. raise Exception("Unknown ARRSTART: %i" % arraydesc['arrstart'])
  353. return arraydesc
  354. def _read_structdesc(f):
  355. '''Function to read in a structure descriptor'''
  356. structdesc = {}
  357. structstart = _read_long(f)
  358. if structstart != 9:
  359. raise Exception("STRUCTSTART should be 9")
  360. structdesc['name'] = _read_string(f)
  361. predef = _read_long(f)
  362. structdesc['ntags'] = _read_long(f)
  363. structdesc['nbytes'] = _read_long(f)
  364. structdesc['predef'] = predef & 1
  365. structdesc['inherits'] = predef & 2
  366. structdesc['is_super'] = predef & 4
  367. if not structdesc['predef']:
  368. structdesc['tagtable'] = [_read_tagdesc(f)
  369. for _ in range(structdesc['ntags'])]
  370. for tag in structdesc['tagtable']:
  371. tag['name'] = _read_string(f)
  372. structdesc['arrtable'] = {tag['name']: _read_arraydesc(f)
  373. for tag in structdesc['tagtable']
  374. if tag['array']}
  375. structdesc['structtable'] = {tag['name']: _read_structdesc(f)
  376. for tag in structdesc['tagtable']
  377. if tag['structure']}
  378. if structdesc['inherits'] or structdesc['is_super']:
  379. structdesc['classname'] = _read_string(f)
  380. structdesc['nsupclasses'] = _read_long(f)
  381. structdesc['supclassnames'] = [
  382. _read_string(f) for _ in range(structdesc['nsupclasses'])]
  383. structdesc['supclasstable'] = [
  384. _read_structdesc(f) for _ in range(structdesc['nsupclasses'])]
  385. STRUCT_DICT[structdesc['name']] = structdesc
  386. else:
  387. if not structdesc['name'] in STRUCT_DICT:
  388. raise Exception("PREDEF=1 but can't find definition")
  389. structdesc = STRUCT_DICT[structdesc['name']]
  390. return structdesc
  391. def _read_tagdesc(f):
  392. '''Function to read in a tag descriptor'''
  393. tagdesc = {'offset': _read_long(f)}
  394. if tagdesc['offset'] == -1:
  395. tagdesc['offset'] = _read_uint64(f)
  396. tagdesc['typecode'] = _read_long(f)
  397. tagflags = _read_long(f)
  398. tagdesc['array'] = tagflags & 4 == 4
  399. tagdesc['structure'] = tagflags & 32 == 32
  400. tagdesc['scalar'] = tagdesc['typecode'] in DTYPE_DICT
  401. # Assume '10'x is scalar
  402. return tagdesc
  403. def _replace_heap(variable, heap):
  404. if isinstance(variable, Pointer):
  405. while isinstance(variable, Pointer):
  406. if variable.index == 0:
  407. variable = None
  408. else:
  409. if variable.index in heap:
  410. variable = heap[variable.index]
  411. else:
  412. warnings.warn("Variable referenced by pointer not found "
  413. "in heap: variable will be set to None")
  414. variable = None
  415. replace, new = _replace_heap(variable, heap)
  416. if replace:
  417. variable = new
  418. return True, variable
  419. elif isinstance(variable, np.core.records.recarray):
  420. # Loop over records
  421. for ir, record in enumerate(variable):
  422. replace, new = _replace_heap(record, heap)
  423. if replace:
  424. variable[ir] = new
  425. return False, variable
  426. elif isinstance(variable, np.core.records.record):
  427. # Loop over values
  428. for iv, value in enumerate(variable):
  429. replace, new = _replace_heap(value, heap)
  430. if replace:
  431. variable[iv] = new
  432. return False, variable
  433. elif isinstance(variable, np.ndarray):
  434. # Loop over values if type is np.object_
  435. if variable.dtype.type is np.object_:
  436. for iv in range(variable.size):
  437. replace, new = _replace_heap(variable.item(iv), heap)
  438. if replace:
  439. variable.itemset(iv, new)
  440. return False, variable
  441. else:
  442. return False, variable
  443. class AttrDict(dict):
  444. '''
  445. A case-insensitive dictionary with access via item, attribute, and call
  446. notations:
  447. >>> d = AttrDict()
  448. >>> d['Variable'] = 123
  449. >>> d['Variable']
  450. 123
  451. >>> d.Variable
  452. 123
  453. >>> d.variable
  454. 123
  455. >>> d('VARIABLE')
  456. 123
  457. >>> d['missing']
  458. Traceback (most recent error last):
  459. ...
  460. KeyError: 'missing'
  461. >>> d.missing
  462. Traceback (most recent error last):
  463. ...
  464. AttributeError: 'AttrDict' object has no attribute 'missing'
  465. '''
  466. def __init__(self, init={}):
  467. dict.__init__(self, init)
  468. def __getitem__(self, name):
  469. return super().__getitem__(name.lower())
  470. def __setitem__(self, key, value):
  471. return super().__setitem__(key.lower(), value)
  472. def __getattr__(self, name):
  473. try:
  474. return self.__getitem__(name)
  475. except KeyError:
  476. raise AttributeError(
  477. f"'{type(self)}' object has no attribute '{name}'") from None
  478. __setattr__ = __setitem__
  479. __call__ = __getitem__
  480. def readsav(file_name, idict=None, python_dict=False,
  481. uncompressed_file_name=None, verbose=False):
  482. """
  483. Read an IDL .sav file.
  484. Parameters
  485. ----------
  486. file_name : str
  487. Name of the IDL save file.
  488. idict : dict, optional
  489. Dictionary in which to insert .sav file variables.
  490. python_dict : bool, optional
  491. By default, the object return is not a Python dictionary, but a
  492. case-insensitive dictionary with item, attribute, and call access
  493. to variables. To get a standard Python dictionary, set this option
  494. to True.
  495. uncompressed_file_name : str, optional
  496. This option only has an effect for .sav files written with the
  497. /compress option. If a file name is specified, compressed .sav
  498. files are uncompressed to this file. Otherwise, readsav will use
  499. the `tempfile` module to determine a temporary filename
  500. automatically, and will remove the temporary file upon successfully
  501. reading it in.
  502. verbose : bool, optional
  503. Whether to print out information about the save file, including
  504. the records read, and available variables.
  505. Returns
  506. -------
  507. idl_dict : AttrDict or dict
  508. If `python_dict` is set to False (default), this function returns a
  509. case-insensitive dictionary with item, attribute, and call access
  510. to variables. If `python_dict` is set to True, this function
  511. returns a Python dictionary with all variable names in lowercase.
  512. If `idict` was specified, then variables are written to the
  513. dictionary specified, and the updated dictionary is returned.
  514. Examples
  515. --------
  516. >>> from os.path import dirname, join as pjoin
  517. >>> import scipy.io as sio
  518. >>> from scipy.io import readsav
  519. Get the filename for an example .sav file from the tests/data directory.
  520. >>> data_dir = pjoin(dirname(sio.__file__), 'tests', 'data')
  521. >>> sav_fname = pjoin(data_dir, 'array_float32_1d.sav')
  522. Load the .sav file contents.
  523. >>> sav_data = readsav(sav_fname)
  524. Get keys of the .sav file contents.
  525. >>> print(sav_data.keys())
  526. dict_keys(['array1d'])
  527. Access a content with a key.
  528. >>> print(sav_data['array1d'])
  529. [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  530. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  531. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  532. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  533. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  534. 0. 0. 0.]
  535. """
  536. # Initialize record and variable holders
  537. records = []
  538. if python_dict or idict:
  539. variables = {}
  540. else:
  541. variables = AttrDict()
  542. # Open the IDL file
  543. f = open(file_name, 'rb')
  544. # Read the signature, which should be 'SR'
  545. signature = _read_bytes(f, 2)
  546. if signature != b'SR':
  547. raise Exception("Invalid SIGNATURE: %s" % signature)
  548. # Next, the record format, which is '\x00\x04' for normal .sav
  549. # files, and '\x00\x06' for compressed .sav files.
  550. recfmt = _read_bytes(f, 2)
  551. if recfmt == b'\x00\x04':
  552. pass
  553. elif recfmt == b'\x00\x06':
  554. if verbose:
  555. print("IDL Save file is compressed")
  556. if uncompressed_file_name:
  557. fout = open(uncompressed_file_name, 'w+b')
  558. else:
  559. fout = tempfile.NamedTemporaryFile(suffix='.sav')
  560. if verbose:
  561. print(" -> expanding to %s" % fout.name)
  562. # Write header
  563. fout.write(b'SR\x00\x04')
  564. # Cycle through records
  565. while True:
  566. # Read record type
  567. rectype = _read_long(f)
  568. fout.write(struct.pack('>l', int(rectype)))
  569. # Read position of next record and return as int
  570. nextrec = _read_uint32(f)
  571. nextrec += _read_uint32(f) * 2**32
  572. # Read the unknown 4 bytes
  573. unknown = f.read(4)
  574. # Check if the end of the file has been reached
  575. if RECTYPE_DICT[rectype] == 'END_MARKER':
  576. modval = np.int64(2**32)
  577. fout.write(struct.pack('>I', int(nextrec) % modval))
  578. fout.write(struct.pack('>I', int((nextrec - (nextrec % modval)) / modval)))
  579. fout.write(unknown)
  580. break
  581. # Find current position
  582. pos = f.tell()
  583. # Decompress record
  584. rec_string = zlib.decompress(f.read(nextrec-pos))
  585. # Find new position of next record
  586. nextrec = fout.tell() + len(rec_string) + 12
  587. # Write out record
  588. fout.write(struct.pack('>I', int(nextrec % 2**32)))
  589. fout.write(struct.pack('>I', int((nextrec - (nextrec % 2**32)) / 2**32)))
  590. fout.write(unknown)
  591. fout.write(rec_string)
  592. # Close the original compressed file
  593. f.close()
  594. # Set f to be the decompressed file, and skip the first four bytes
  595. f = fout
  596. f.seek(4)
  597. else:
  598. raise Exception("Invalid RECFMT: %s" % recfmt)
  599. # Loop through records, and add them to the list
  600. while True:
  601. r = _read_record(f)
  602. records.append(r)
  603. if 'end' in r:
  604. if r['end']:
  605. break
  606. # Close the file
  607. f.close()
  608. # Find heap data variables
  609. heap = {}
  610. for r in records:
  611. if r['rectype'] == "HEAP_DATA":
  612. heap[r['heap_index']] = r['data']
  613. # Find all variables
  614. for r in records:
  615. if r['rectype'] == "VARIABLE":
  616. replace, new = _replace_heap(r['data'], heap)
  617. if replace:
  618. r['data'] = new
  619. variables[r['varname'].lower()] = r['data']
  620. if verbose:
  621. # Print out timestamp info about the file
  622. for record in records:
  623. if record['rectype'] == "TIMESTAMP":
  624. print("-"*50)
  625. print("Date: %s" % record['date'])
  626. print("User: %s" % record['user'])
  627. print("Host: %s" % record['host'])
  628. break
  629. # Print out version info about the file
  630. for record in records:
  631. if record['rectype'] == "VERSION":
  632. print("-"*50)
  633. print("Format: %s" % record['format'])
  634. print("Architecture: %s" % record['arch'])
  635. print("Operating System: %s" % record['os'])
  636. print("IDL Version: %s" % record['release'])
  637. break
  638. # Print out identification info about the file
  639. for record in records:
  640. if record['rectype'] == "IDENTIFICATON":
  641. print("-"*50)
  642. print("Author: %s" % record['author'])
  643. print("Title: %s" % record['title'])
  644. print("ID Code: %s" % record['idcode'])
  645. break
  646. # Print out descriptions saved with the file
  647. for record in records:
  648. if record['rectype'] == "DESCRIPTION":
  649. print("-"*50)
  650. print("Description: %s" % record['description'])
  651. break
  652. print("-"*50)
  653. print("Successfully read %i records of which:" %
  654. (len(records)))
  655. # Create convenience list of record types
  656. rectypes = [r['rectype'] for r in records]
  657. for rt in set(rectypes):
  658. if rt != 'END_MARKER':
  659. print(" - %i are of type %s" % (rectypes.count(rt), rt))
  660. print("-"*50)
  661. if 'VARIABLE' in rectypes:
  662. print("Available variables:")
  663. for var in variables:
  664. print(" - %s [%s]" % (var, type(variables[var])))
  665. print("-"*50)
  666. if idict:
  667. for var in variables:
  668. idict[var] = variables[var]
  669. return idict
  670. else:
  671. return variables