diff --git a/.gitignore b/.gitignore index 8d35cb3..ad3b40d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ __pycache__ *.pyc +build/ +pygor.egg-info/ + +dist/ +.venv/ +.pytest_cache/ \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/COPYING.LESSER b/COPYING.LESSER deleted file mode 100644 index fc8a5de..0000000 --- a/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index f6725a8..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include COPYING -include COPYING.LESSER diff --git a/README b/README deleted file mode 100644 index dc21b62..0000000 --- a/README +++ /dev/null @@ -1,138 +0,0 @@ -==== -Igor -==== - -:Authors: W. Trevor King ; - Paul Kienzle -:License: GNU General Public License, version 3+ - -Python parsers for Igor Binary Waves (.ibw) and Packed Experiment -(.pxp) files written by WaveMetrics' IGOR Pro software. - -Installation -============ - -Packages --------- - -If `igor` already exists in your package manager's repository, you -should install `igor` in the usual way. - -Gentoo -~~~~~~ - -I've packaged `igor` for Gentoo. You need layman_ and my `wtk -overlay`_. Install with:: - - # emerge -av app-portage/layman - # layman --add wtk - # emerge -av sci-misc/igor - -Dependencies ------------- - -If you're installing by hand or packaging `igor` for another -distribution, you'll need the following dependencies: - -=========== ================= ============================ -Package Debian_ Gentoo_ -=========== ================= ============================ -Numpy_ python-numpy dev-python/numpy -Matplotlib_ python-matplotlib dev-python/matplotlib -Nose_ python-nose dev-python/nose -=========== ================= ============================ - -Installing by hand ------------------- - -`igor` is available as a Git_ repository:: - - $ git clone git://tremily.us/igor.git - -See the homepage_ for details. To install the checkout, run the -standard:: - - $ python setup.py install - -You can also automate this installation with pip_:: - - $ pip install igor - -Usage -===== - -See the docstrings and unit tests for examples using the Python API. -The package also installs to scripts, ``igorbinarywave.py`` and -``igorpackedexperiment.py`` which can be used to dump files to stdout. -For details on their usage, use the ``--help`` option. For example:: - - $ igorbinarywave.py --help - -For users transitioning from igor.py_, there's a compatibility module -exposing the old interface. Just change:: - - import igor - -to:: - - import igor.igorpy as igor - -in your calling code. - -Testing -======= - -Run internal unit tests with:: - - $ nosetests --with-doctest --doctest-tests igor test - -The data in the ``test/data`` directory is in the Git repository, but -it is not bundled with the source code. If you want the test data, -you'll have to clone the Git repository or download a snapshot. - -Licence -======= - -This project is distributed under the `GNU Lesser General Public -License Version 3`_ or greater, see the ``COPYING`` file distributed -with the project for details. - -Maintenance -=========== - -Maintainer ----------- - -W. Trevor King -wking@tremily.us -Copyright 2008-2012 - -Release procedure ------------------ - -When a new version of the package is ready, increment __version__ -in ``igor/__init__.py`` and run update-copyright_:: - - $ update-copyright.py - -to update the copyright blurbs. Then run:: - - $ python setup.py sdist upload - -This will place a new version on PyPI. - - -.. _layman: http://layman.sourceforge.net/ -.. _wtk overlay: http://blog.tremily.us/posts/Gentoo_overlay/ -.. _Debian: http://www.debian.org/ -.. _Gentoo: http://www.gentoo.org/ -.. _NumPy: http://numpy.scipy.org/ -.. _Matplotlib: http://matplotlib.sourceforge.net/ -.. _Nose: http://somethingaboutorange.com/mrl/projects/nose/ -.. _Git: http://git-scm.com/ -.. _homepage: http://blog.tremily.us/posts/igor/ -.. _pip: http://pypi.python.org/pypi/pip -.. _igor.py: http://pypi.python.org/pypi/igor.py -.. _GNU Lesser General Public License Version 3: - http://www.gnu.org/licenses/lgpl.txt -.. _update-copyright: http://blog.tremily.us/posts/update-copyright/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..2de0aa3 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +Pygor is a package for loading binary IGOR files. It's forked from [chstan's igorpy](https://github.com/chstan/igorpy) which is forked from [igor](https://github.com/wking/igor). The main purpose of pygor is to facilitate the loading of pxt files for [arpes-lite](https://github.com/jgobbo/arpes-lite). + +Installation +------------ + +pygor is most easily installed with pip: `pip install pygor` + +you can also clone the repo and install with [uv](https://docs.astral.sh/uv/) + +Usage +----- + +Standard usage is straightforward: + +```python +from pygor import load + +byte_order = "<" # options are "<", "=", ">" +data = load(IGOR_FILE_NAME, initial_byte_order=byte_order) +``` + diff --git a/bin/igorbinarywave.py b/bin/igorbinarywave.py deleted file mode 100755 index 70c1bc3..0000000 --- a/bin/igorbinarywave.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2012 W. Trevor King -# -# This file is part of igor. -# -# igor is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# igor is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . - -"IBW -> ASCII conversion" - -import pprint - -import numpy - -from igor.binarywave import load -from igor.script import Script - - -class WaveScript (Script): - def _run(self, args): - wave = load(args.infile) - numpy.savetxt( - args.outfile, wave['wave']['wData'], fmt='%g', delimiter='\t') - self.plot_wave(args, wave) - if args.verbose > 0: - wave['wave'].pop('wData') - pprint.pprint(wave) - - -s = WaveScript(description=__doc__) -s.run() diff --git a/bin/igorpackedexperiment.py b/bin/igorpackedexperiment.py deleted file mode 100755 index 0a08444..0000000 --- a/bin/igorpackedexperiment.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2012 W. Trevor King -# -# This file is part of igor. -# -# igor is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# igor is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . - -"PXP -> ASCII conversion" - -import pprint - -from igor.packed import load, walk -from igor.record.wave import WaveRecord -from igor.script import Script - - -class PackedScript (Script): - def _run(self, args): - self.args = args - records,filesystem = load(args.infile) - if hasattr(args.outfile, 'write'): - f = args.outfile # filename is actually a stream object - else: - f = open(args.outfile, 'w') - try: - f.write(pprint.pformat(records)) - f.write('\n') - finally: - if f != args.outfile: - f.close() - if args.verbose > 0: - pprint.pprint(filesystem) - walk(filesystem, self._plot_wave_callback) - - def _plot_wave_callback(self, dirpath, key, value): - if isinstance(value, WaveRecord): - self.plot_wave(self.args, value.wave, title=dirpath + [key]) - - -s = PackedScript( - description=__doc__, filetype='IGOR Packed Experiment (.pxp) file') -s.run() diff --git a/igor/binarywave.py b/igor/binarywave.py deleted file mode 100644 index 6d87d14..0000000 --- a/igor/binarywave.py +++ /dev/null @@ -1,655 +0,0 @@ -# Copyright (C) 2010-2012 W. Trevor King -# -# This file is part of igor. -# -# igor is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# igor is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . - -"Read IGOR Binary Wave files into Numpy arrays." - -# Based on WaveMetric's Technical Note 003, "Igor Binary Format" -# ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip -# From ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN000.txt -# We place no restrictions on copying Technical Notes, with the -# exception that you cannot resell them. So read, enjoy, and -# share. We hope IGOR Technical Notes will provide you with lots of -# valuable information while you are developing IGOR applications. - -from __future__ import absolute_import -import array as _array -import struct as _struct -import sys as _sys -import types as _types - -import numpy as _numpy - -from . import LOG as _LOG -from .struct import Structure as _Structure -from .struct import DynamicStructure as _DynamicStructure -from .struct import Field as _Field -from .struct import DynamicField as _DynamicField -from .util import assert_null as _assert_null -from .util import byte_order as _byte_order -from .util import need_to_reorder_bytes as _need_to_reorder_bytes -from .util import checksum as _checksum - - -# Numpy doesn't support complex integers by default, see -# http://mail.python.org/pipermail/python-dev/2002-April/022408.html -# http://mail.scipy.org/pipermail/numpy-discussion/2007-October/029447.html -# So we roll our own types. See -# http://docs.scipy.org/doc/numpy/user/basics.rec.html -# http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html -complexInt8 = _numpy.dtype([('real', _numpy.int8), ('imag', _numpy.int8)]) -complexInt16 = _numpy.dtype([('real', _numpy.int16), ('imag', _numpy.int16)]) -complexInt32 = _numpy.dtype([('real', _numpy.int32), ('imag', _numpy.int32)]) -complexUInt8 = _numpy.dtype([('real', _numpy.uint8), ('imag', _numpy.uint8)]) -complexUInt16 = _numpy.dtype( - [('real', _numpy.uint16), ('imag', _numpy.uint16)]) -complexUInt32 = _numpy.dtype( - [('real', _numpy.uint32), ('imag', _numpy.uint32)]) - - -class StaticStringField (_DynamicField): - _null_terminated = False - _array_size_field = None - def __init__(self, *args, **kwargs): - if 'array' not in kwargs: - kwargs['array'] = True - super(StaticStringField, self).__init__(*args, **kwargs) - - def post_unpack(self, parents, data): - wave_structure = parents[-1] - wave_data = self._get_structure_data(parents, data, wave_structure) - d = self._normalize_string(wave_data[self.name]) - wave_data[self.name] = d - - def _normalize_string(self, d): - if isinstance(d, bytes): - pass - elif hasattr(d, 'tobytes'): - d = d.tobytes() - elif hasattr(d, 'tostring'): # Python 2 compatibility - d = d.tostring() - else: - d = b''.join(d) - if self._array_size_field: - start = 0 - strings = [] - for count in self.counts: - end = start + count - if end > start: - strings.append(d[start:end]) - if self._null_terminated: - strings[-1] = strings[-1].split(b'\x00', 1)[0] - start = end - elif self._null_terminated: - d = d.split(b'\x00', 1)[0] - return d - - -class NullStaticStringField (StaticStringField): - _null_terminated = True - - -# Begin IGOR constants and typedefs from IgorBin.h - -# From IgorMath.h -TYPE_TABLE = { # (key: integer flag, value: numpy dtype) - 0:None, # Text wave, not handled in ReadWave.c - 1:_numpy.complex, # NT_CMPLX, makes number complex. - 2:_numpy.float32, # NT_FP32, 32 bit fp numbers. - 3:_numpy.complex64, - 4:_numpy.float64, # NT_FP64, 64 bit fp numbers. - 5:_numpy.complex128, - 8:_numpy.int8, # NT_I8, 8 bit signed integer. Requires Igor Pro - # 2.0 or later. - 9:complexInt8, - 0x10:_numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor - # Pro 2.0 or later. - 0x11:complexInt16, - 0x20:_numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor - # Pro 2.0 or later. - 0x21:complexInt32, -# 0x40:None, # NT_UNSIGNED, Makes above signed integers -# # unsigned. Requires Igor Pro 3.0 or later. - 0x48:_numpy.uint8, - 0x49:complexUInt8, - 0x50:_numpy.uint16, - 0x51:complexUInt16, - 0x60:_numpy.uint32, - 0x61:complexUInt32, -} - -# From wave.h -MAXDIMS = 4 - -# From binary.h -BinHeader1 = _Structure( # `version` field pulled out into Wave - name='BinHeader1', - fields=[ - _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), - _Field('h', 'checksum', help='Checksum over this header and the wave header.'), - ]) - -BinHeader2 = _Structure( # `version` field pulled out into Wave - name='BinHeader2', - fields=[ - _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), - _Field('l', 'noteSize', help='The size of the note text.'), - _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('h', 'checksum', help='Checksum over this header and the wave header.'), - ]) - -BinHeader3 = _Structure( # `version` field pulled out into Wave - name='BinHeader3', - fields=[ - _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), - _Field('l', 'noteSize', help='The size of the note text.'), - _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), - _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('h', 'checksum', help='Checksum over this header and the wave header.'), - ]) - -BinHeader5 = _Structure( # `version` field pulled out into Wave - name='BinHeader5', - fields=[ - _Field('h', 'checksum', help='Checksum over this header and the wave header.'), - _Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'), - _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), - _Field('l', 'noteSize', help='The size of the note text.'), - _Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'), - _Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS, array=True), - _Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS, array=True), - _Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'), - _Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'), - ]) - - -# From wave.h -MAX_WAVE_NAME2 = 18 # Maximum length of wave name in version 1 and 2 - # files. Does not include the trailing null. -MAX_WAVE_NAME5 = 31 # Maximum length of wave name in version 5 - # files. Does not include the trailing null. -MAX_UNIT_CHARS = 3 - -# Header to an array of waveform data. - -# `wData` field pulled out into DynamicWaveDataField1 -WaveHeader2 = _DynamicStructure( - name='WaveHeader2', - fields=[ - _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), - _Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'), - NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2), - _Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'), - _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1, array=True), - _Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1, array=True), - _Field('l', 'npnts', help='Number of data points in wave.'), - _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('d', 'hsA', help='X value for point p = hsA*p + hsB'), - _Field('d', 'hsB', help='X value for point p = hsA*p + hsB'), - _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('h', 'fsValid', help='True if full scale values have meaning.'), - _Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max' - _Field('d', 'botFullScale', help='The min full scale value for wave.'), - _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('L', 'creationDate', help='DateTime of creation. Not used in version 1 files.'), - _Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2, array=True), - _Field('L', 'modDate', help='DateTime of last modification.'), - _Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'), - ]) - -# `sIndices` pointer unset (use Wave5_data['sIndices'] instead). This -# field is filled in by DynamicStringIndicesDataField. -# `wData` field pulled out into DynamicWaveDataField5 -WaveHeader5 = _DynamicStructure( - name='WaveHeader5', - fields=[ - _Field('P', 'next', help='link to next wave in linked list.'), - _Field('L', 'creationDate', help='DateTime of creation.'), - _Field('L', 'modDate', help='DateTime of last modification.'), - _Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'), - _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), - _Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6, array=True), - _Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'), - NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1), - _Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'), - # Dimensioning info. [0] == rows, [1] == cols etc - _Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS, array=True), - _Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS, array=True), - _Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS, array=True), - # SI units - _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1, array=True), - _Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1), array=True), - _Field('h', 'fsValid', help='TRUE if full scale values have meaning.'), - _Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min" - _Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min" - _Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS, array=True), - _Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS, array=True), - _Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16, array=True), - # The following stuff is considered private to Igor. - _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'), - ]) - - -class DynamicWaveDataField1 (_DynamicField): - def pre_pack(self, parents, data): - raise NotImplementedError() - - def pre_unpack(self, parents, data): - full_structure = parents[0] - wave_structure = parents[-1] - wave_header_structure = wave_structure.fields[1].format - wave_data = self._get_structure_data(parents, data, wave_structure) - version = data['version'] - bin_header = wave_data['bin_header'] - wave_header = wave_data['wave_header'] - - self.count = wave_header['npnts'] - self.data_size = self._get_size(bin_header, wave_header_structure.size) - - type_ = TYPE_TABLE.get(wave_header['type'], None) - if type_: - self.shape = self._get_shape(bin_header, wave_header) - else: # text wave - type_ = _numpy.dtype('S1') - self.shape = (self.data_size,) - # dtype() wrapping to avoid numpy.generic and - # getset_descriptor issues with the builtin numpy types - # (e.g. int32). It has no effect on our local complex - # integers. - self.dtype = _numpy.dtype(type_).newbyteorder( - wave_structure.byte_order) - if (version == 3 and - self.count > 0 and - bin_header['formulaSize'] > 0 and - self.data_size == 0): - """From TN003: - - Igor Pro 2.00 included support for dependency formulae. If - a wave was governed by a dependency formula then the - actual wave data was not written to disk for that wave, - because on loading the wave Igor could recalculate the - data. However,this prevented the wave from being loaded - into an experiment other than the original - experiment. Consequently, in a version of Igor Pro 3.0x, - we changed it so that the wave data was written even if - the wave was governed by a dependency formula. When - reading a binary wave file, you can detect that the wave - file does not contain the wave data by examining the - wfmSize, formulaSize and npnts fields. If npnts is greater - than zero and formulaSize is greater than zero and - the waveDataSize as calculated above is zero, then this is - a file governed by a dependency formula that was written - without the actual wave data. - """ - self.shape = (0,) - elif TYPE_TABLE.get(wave_header['type'], None) is not None: - assert self.data_size == self.count * self.dtype.itemsize, ( - self.data_size, self.count, self.dtype.itemsize, self.dtype) - else: - assert self.data_size >= 0, ( - bin_header['wfmSize'], wave_header_structure.size) - - def _get_size(self, bin_header, wave_header_size): - return bin_header['wfmSize'] - wave_header_size - 16 - - def _get_shape(self, bin_header, wave_header): - return (self.count,) - - def unpack(self, stream): - data_b = stream.read(self.data_size) - try: - data = _numpy.ndarray( - shape=self.shape, - dtype=self.dtype, - buffer=data_b, - order='F', - ) - except: - _LOG.error( - 'could not reshape data from {} to {}'.format( - self.shape, data_b)) - raise - return data - - -class DynamicWaveDataField5 (DynamicWaveDataField1): - "Adds support for multidimensional data." - def _get_size(self, bin_header, wave_header_size): - return bin_header['wfmSize'] - wave_header_size - - def _get_shape(self, bin_header, wave_header): - return [n for n in wave_header['nDim'] if n > 0] or (0,) - - -# End IGOR constants and typedefs from IgorBin.h - - -class DynamicStringField (StaticStringField): - _size_field = None - - def pre_unpack(self, parents, data): - size = self._get_size_data(parents, data) - if self._array_size_field: - self.counts = size - self.count = sum(self.counts) - else: - self.count = size - self.setup() - - def _get_size_data(self, parents, data): - wave_structure = parents[-1] - wave_data = self._get_structure_data(parents, data, wave_structure) - bin_header = wave_data['bin_header'] - return bin_header[self._size_field] - - -class DynamicWaveNoteField (DynamicStringField): - _size_field = 'noteSize' - - -class DynamicDependencyFormulaField (DynamicStringField): - """Optional wave dependency formula - - Excerpted from TN003: - - A wave has a dependency formula if it has been bound by a - statement such as "wave0 := sin(x)". In this example, the - dependency formula is "sin(x)". The formula is stored with - no trailing null byte. - """ - _size_field = 'formulaSize' - # Except when it is stored with a trailing null byte :p. See, for - # example, test/data/mac-version3Dependent.ibw. - _null_terminated = True - - -class DynamicDataUnitsField (DynamicStringField): - """Optional extended data units data - - Excerpted from TN003: - - dataUnits - Present in versions 1, 2, 3, 5. The dataUnits field - stores the units for the data represented by the wave. It is a C - string terminated with a null character. This field supports - units of 0 to 3 bytes. In version 1, 2 and 3 files, longer units - can not be represented. In version 5 files, longer units can be - stored using the optional extended data units section of the - file. - """ - _size_field = 'dataEUnitsSize' - - -class DynamicDimensionUnitsField (DynamicStringField): - """Optional extended dimension units data - - Excerpted from TN003: - - xUnits - Present in versions 1, 2, 3. The xUnits field stores the - X units for a wave. It is a C string terminated with a null - character. This field supports units of 0 to 3 bytes. In - version 1, 2 and 3 files, longer units can not be represented. - - dimUnits - Present in version 5 only. This field is an array of 4 - strings, one for each possible wave dimension. Each string - supports units of 0 to 3 bytes. Longer units can be stored using - the optional extended dimension units section of the file. - """ - _size_field = 'dimEUnitsSize' - _array_size_field = True - - -class DynamicLabelsField (DynamicStringField): - """Optional dimension label data - - From TN003: - - If the wave has dimension labels for dimension d then the - dimLabelsSize[d] field of the BinHeader5 structure will be - non-zero. - - A wave will have dimension labels if a SetDimLabel command has - been executed on it. - - A 3 point 1D wave has 4 dimension labels. The first dimension - label is the label for the dimension as a whole. The next three - dimension labels are the labels for rows 0, 1, and 2. When Igor - writes dimension labels to disk, it writes each dimension label as - a C string (null-terminated) in a field of 32 bytes. - """ - _size_field = 'dimLabelsSize' - _array_size_field = True - - def post_unpack(self, parents, data): - wave_structure = parents[-1] - wave_data = self._get_structure_data(parents, data, wave_structure) - bin_header = wave_data['bin_header'] - d = wave_data[self.name] - dim_labels = [] - start = 0 - for size in bin_header[self._size_field]: - end = start + size - if end > start: - dim_data = d[start:end] - chunks = [] - for i in range(size//32): - chunks.append(dim_data[32*i:32*(i+1)]) - labels = [b''] - for chunk in chunks: - labels[-1] = labels[-1] + b''.join(chunk) - if b'\x00' in chunk: - labels.append(b'') - labels.pop(-1) - start = end - else: - labels = [] - dim_labels.append(labels) - wave_data[self.name] = dim_labels - - -class DynamicStringIndicesDataField (_DynamicField): - """String indices used for text waves only - """ - def pre_pack(self, parents, data): - raise NotImplementedError() - - def pre_unpack(self, parents, data): - wave_structure = parents[-1] - wave_data = self._get_structure_data(parents, data, wave_structure) - bin_header = wave_data['bin_header'] - wave_header = wave_data['wave_header'] - self.string_indices_size = bin_header['sIndicesSize'] - self.count = self.string_indices_size // 4 - if self.count: # make sure we're in a text wave - assert TYPE_TABLE[wave_header['type']] is None, wave_header - self.setup() - - def post_unpack(self, parents, data): - if not self.count: - return - wave_structure = parents[-1] - wave_data = self._get_structure_data(parents, data, wave_structure) - wave_header = wave_data['wave_header'] - wdata = wave_data['wData'] - strings = [] - start = 0 - for i,offset in enumerate(wave_data['sIndices']): - if offset > start: - chars = wdata[start:offset] - strings.append(b''.join(chars)) - start = offset - elif offset == start: - strings.append(b'') - else: - raise ValueError((offset, wave_data['sIndices'])) - wdata = _numpy.array(strings) - shape = [n for n in wave_header['nDim'] if n > 0] or (0,) - try: - wdata = wdata.reshape(shape) - except ValueError: - _LOG.error( - 'could not reshape strings from {} to {}'.format( - shape, wdata.shape)) - raise - wave_data['wData'] = wdata - - -class DynamicVersionField (_DynamicField): - def pre_pack(self, parents, byte_order): - raise NotImplementedError() - - def post_unpack(self, parents, data): - wave_structure = parents[-1] - wave_data = self._get_structure_data(parents, data, wave_structure) - version = wave_data['version'] - if wave_structure.byte_order in '@=': - need_to_reorder_bytes = _need_to_reorder_bytes(version) - wave_structure.byte_order = _byte_order(need_to_reorder_bytes) - _LOG.debug( - 'get byte order from version: {} (reorder? {})'.format( - wave_structure.byte_order, need_to_reorder_bytes)) - else: - need_to_reorder_bytes = False - - old_format = wave_structure.fields[-1].format - if version == 1: - wave_structure.fields[-1].format = Wave1 - elif version == 2: - wave_structure.fields[-1].format = Wave2 - elif version == 3: - wave_structure.fields[-1].format = Wave3 - elif version == 5: - wave_structure.fields[-1].format = Wave5 - elif not need_to_reorder_bytes: - raise ValueError( - 'invalid binary wave version: {}'.format(version)) - - if wave_structure.fields[-1].format != old_format: - _LOG.debug('change wave headers from {} to {}'.format( - old_format, wave_structure.fields[-1].format)) - wave_structure.setup() - elif need_to_reorder_bytes: - wave_structure.setup() - - # we might need to unpack again with the new byte order - return need_to_reorder_bytes - - -class DynamicWaveField (_DynamicField): - def post_unpack(self, parents, data): - return - raise NotImplementedError() # TODO - checksum_size = bin.size + wave.size - wave_structure = parents[-1] - if version == 5: - # Version 5 checksum does not include the wData field. - checksum_size -= 4 - c = _checksum(b, parents[-1].byte_order, 0, checksum_size) - if c != 0: - raise ValueError( - ('This does not appear to be a valid Igor binary wave file. ' - 'Error in checksum: should be 0, is {}.').format(c)) - -Wave1 = _DynamicStructure( - name='Wave1', - fields=[ - _Field(BinHeader1, 'bin_header', help='Binary wave header'), - _Field(WaveHeader2, 'wave_header', help='Wave header'), - DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), - ]) - -Wave2 = _DynamicStructure( - name='Wave2', - fields=[ - _Field(BinHeader2, 'bin_header', help='Binary wave header'), - _Field(WaveHeader2, 'wave_header', help='Wave header'), - DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), - _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16, array=True), - DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0, array=True), - ]) - -Wave3 = _DynamicStructure( - name='Wave3', - fields=[ - _Field(BinHeader3, 'bin_header', help='Binary wave header'), - _Field(WaveHeader2, 'wave_header', help='Wave header'), - DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), - _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16, array=True), - DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0, array=True), - DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula', count=0, array=True), - ]) - -Wave5 = _DynamicStructure( - name='Wave5', - fields=[ - _Field(BinHeader5, 'bin_header', help='Binary wave header'), - _Field(WaveHeader5, 'wave_header', help='Wave header'), - DynamicWaveDataField5('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), - DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula.', count=0, array=True), - DynamicWaveNoteField('c', 'note', help='Optional wave note data.', count=0, array=True), - DynamicDataUnitsField('c', 'data_units', help='Optional extended data units data.', count=0, array=True), - DynamicDimensionUnitsField('c', 'dimension_units', help='Optional dimension label data', count=0, array=True), - DynamicLabelsField('c', 'labels', help="Optional dimension label data", count=0, array=True), - DynamicStringIndicesDataField('P', 'sIndices', help='Dynamic string indices for text waves.', count=0, array=True), - ]) - -Wave = _DynamicStructure( - name='Wave', - fields=[ - DynamicVersionField('h', 'version', help='Version number for backwards compatibility.'), - DynamicWaveField(Wave1, 'wave', help='The rest of the wave data.'), - ]) - - -def load(filename): - if hasattr(filename, 'read'): - f = filename # filename is actually a stream object - else: - f = open(filename, 'rb') - try: - Wave.byte_order = '=' - Wave.setup() - data = Wave.unpack_stream(f) - finally: - if not hasattr(filename, 'read'): - f.close() - - return data - - -def save(filename): - raise NotImplementedError diff --git a/igor/igorpy.py b/igor/igorpy.py deleted file mode 100644 index b28de2e..0000000 --- a/igor/igorpy.py +++ /dev/null @@ -1,283 +0,0 @@ -# This program is in the public domain -"""`igor.py` compatibility layer on top of the `igor` package. - -igor.load('filename') or igor.loads('data') loads the content of an igore file -into memory as a folder structure. - -Returns the root folder. - -Folders have name, path and children. -Children can be indexed by folder[i] or by folder['name']. -To see the whole tree, use: print folder.format() - -The usual igor folder types are given in the technical reports -PTN003.ifn and TN003.ifn. -""" -from __future__ import absolute_import -import io as _io -import locale as _locale -import re as _re -import sys as _sys - -import numpy as _numpy - -from .binarywave import MAXDIMS as _MAXDIMS -from .packed import load as _load -from .record.base import UnknownRecord as _UnknownRecord -from .record.folder import FolderStartRecord as _FolderStartRecord -from .record.folder import FolderEndRecord as _FolderEndRecord -from .record.history import HistoryRecord as _HistoryRecord -from .record.history import GetHistoryRecord as _GetHistoryRecord -from .record.history import RecreationRecord as _RecreationRecord -from .record.packedfile import PackedFileRecord as _PackedFileRecord -from .record.procedure import ProcedureRecord as _ProcedureRecord -from .record.wave import WaveRecord as _WaveRecord -from .record.variables import VariablesRecord as _VariablesRecord - - -__version__='0.10' - - -ENCODING = _locale.getpreferredencoding() or _sys.getdefaultencoding() -PYKEYWORDS = set(('and','as','assert','break','class','continue', - 'def','elif','else','except','exec','finally', - 'for','global','if','import','in','is','lambda', - 'or','pass','print','raise','return','try','with', - 'yield')) -PYID = _re.compile(r"^[^\d\W]\w*$", _re.UNICODE) -def valid_identifier(s): - """Check if a name is a valid identifier""" - return PYID.match(s) and s not in PYKEYWORDS - - -class IgorObject(object): - """ Parent class for all objects the parser can return """ - pass - -class Variables(IgorObject): - """ - Contains system numeric variables (e.g., K0) and user numeric and string variables. - """ - def __init__(self, record): - self.sysvar = record.variables['variables']['sysVars'] - self.uservar = record.variables['variables']['userVars'] - self.userstr = record.variables['variables']['userStrs'] - self.depvar = record.variables['variables'].get('dependentVars', {}) - self.depstr = record.variables['variables'].get('dependentStrs', {}) - - def format(self, indent=0): - return " "*indent+""\ - %(len(self.sysvar), - len(self.uservar)+len(self.userstr), - len(self.depvar)+len(self.depstr)) - -class History(IgorObject): - """ - Contains the experiment's history as plain text. - """ - def __init__(self, data): - self.data = data - def format(self, indent=0): - return " "*indent+"" - -class Wave(IgorObject): - """ - Contains the data for a wave - """ - def __init__(self, record): - d = record.wave['wave'] - self.name = d['wave_header']['bname'].decode(ENCODING) - self.data = d['wData'] - self.fs = d['wave_header']['fsValid'] - self.fstop = d['wave_header']['topFullScale'] - self.fsbottom = d['wave_header']['botFullScale'] - version = record.wave['version'] - if version in [1,2,3]: - dims = [d['wave_header']['npnts']] + [0]*(_MAXDIMS-1) - sfA = [d['wave_header']['hsA']] + [0]*(_MAXDIMS-1) - sfB = [d['wave_header']['hsB']] + [0]*(_MAXDIMS-1) - self.data_units = [d['wave_header']['dataUnits']] - self.axis_units = [d['wave_header']['xUnits']] - else: - dims = d['wave_header']['nDim'] - sfA = d['wave_header']['sfA'] - sfB = d['wave_header']['sfB'] - # TODO find example with multiple data units - if version == 5: - self.data_units = [d['data_units'].decode(ENCODING)] - self.axis_units = [b''.join(d).decode(ENCODING) - for d in d['wave_header']['dimUnits']] - else: - self.data_units = [d['data_units'].decode(ENCODING)] - self.axis_units = [d['dimension_units'].decode(ENCODING)] - - self.data_units.extend(['']*(_MAXDIMS-len(self.data_units))) - self.data_units = tuple(self.data_units) - self.axis_units.extend(['']*(_MAXDIMS-len(self.axis_units))) - self.axis_units = tuple(self.axis_units) - self.axis = [_numpy.linspace(b,b + a * (c - 1),c) for a,b,c in zip(sfA, sfB, dims)] - self.formula = d.get('formula', '') - self.notes = d.get('note', '') - def format(self, indent=0): - if isinstance(self.data, list): - type,size = "text", "%d"%len(self.data) - else: - type,size = "data", "x".join(str(d) for d in self.data.shape) - return " "*indent+"%s %s (%s)"%(self.name, type, size) - - def __array__(self): - return self.data - - __repr__ = __str__ = lambda s: "" % s.format() - -class Recreation(IgorObject): - """ - Contains the experiment's recreation procedures as plain text. - """ - def __init__(self, data): - self.data = data - def format(self, indent=0): - return " "*indent + "" -class Procedure(IgorObject): - """ - Contains the experiment's main procedure window text as plain text. - """ - def __init__(self, data): - self.data = data - def format(self, indent=0): - return " "*indent + "" -class GetHistory(IgorObject): - """ - Not a real record but rather, a message to go back and read the history text. - - The reason for GetHistory is that IGOR runs Recreation when it loads the - datafile. This puts entries in the history that shouldn't be there. The - GetHistory entry simply says that the Recreation has run, and the History - can be restored from the previously saved value. - """ - def __init__(self, data): - self.data = data - def format(self, indent=0): - return " "*indent + "" -class PackedFile(IgorObject): - """ - Contains the data for a procedure file or notebook in packed form. - """ - def __init__(self, data): - self.data = data - def format(self, indent=0): - return " "*indent + "" -class Unknown(IgorObject): - """ - Record type not documented in PTN003/TN003. - """ - def __init__(self, data, type): - self.data = data - self.type = type - def format(self, indent=0): - return " "*indent + ""%self.type - - -class Folder(IgorObject): - """ - Hierarchical record container. - """ - def __init__(self, path): - self.name = path[-1] - self.path = path - self.children = [] - - def __getitem__(self, key): - if isinstance(key, int): - return self.children[key] - else: - for r in self.children: - if isinstance(r, (Folder,Wave)) and r.name == key: - return r - raise KeyError("Folder %s does not exist"%key) - - def __str__(self): - return "" % "/".join(self.path) - - __repr__ = __str__ - - def append(self, record): - """ - Add a record to the folder. - """ - self.children.append(record) - try: - # Record may not have a name, the name may be invalid, or it - # may already be in use. The noname case will be covered by - # record.name raising an attribute error. The others we need - # to test for explicitly. - if valid_identifier(record.name) and not hasattr(self, record.name): - setattr(self, record.name, record) - except AttributeError: - pass - - def format(self, indent=0): - parent = " "*indent+self.name - children = [r.format(indent=indent+2) for r in self.children] - return "\n".join([parent]+children) - - -def loads(s, **kwargs): - """Load an igor file from string""" - stream = _io.BytesIO(s) - return load(stream, **kwargs) - -def load(filename, **kwargs): - """Load an igor file""" - try: - packed_experiment = _load( - filename, initial_byte_order=kwargs.pop('initial_byte_order', '=')) - except ValueError as e: - if e.args[0].startswith('not enough data for the next record header'): - raise IOError('invalid record header; bad pxp file?') - elif e.args[0].startswith('not enough data for the next record'): - raise IOError('final record too long; bad pxp file?') - raise - return _convert(packed_experiment, **kwargs) - -def _convert(packed_experiment, ignore_unknown=True): - records, filesystem = packed_experiment - stack = [Folder(path=['root'])] - for record in records: - if isinstance(record, _UnknownRecord): - if ignore_unknown: - continue - else: - r = Unknown(record.data, type=record.header['recordType']) - elif isinstance(record, _GetHistoryRecord): - r = GetHistory(record.text) - elif isinstance(record, _HistoryRecord): - r = History(record.text) - elif isinstance(record, _PackedFileRecord): - r = PackedFile(record.text) - elif isinstance(record, _ProcedureRecord): - r = Procedure(record.text) - elif isinstance(record, _RecreationRecord): - r = Recreation(record.text) - elif isinstance(record, _VariablesRecord): - r = Variables(record) - elif isinstance(record, _WaveRecord): - r = Wave(record) - else: - r = None - - if isinstance(record, _FolderStartRecord): - path = stack[-1].path + [ - record.null_terminated_text.decode(ENCODING)] - folder = Folder(path) - stack[-1].append(folder) - stack.append(folder) - elif isinstance(record, _FolderEndRecord): - stack.pop() - elif r is None: - raise NotImplementedError(record) - else: - stack[-1].append(r) - if len(stack) != 1: - raise IOError("FolderStart records do not match FolderEnd records") - return stack[0] diff --git a/igor/record/variables.py b/igor/record/variables.py deleted file mode 100644 index a8eaccf..0000000 --- a/igor/record/variables.py +++ /dev/null @@ -1,319 +0,0 @@ -# Copyright (C) 2012 W. Trevor King -# -# This file is part of igor. -# -# igor is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# igor is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . - -import io as _io - -from .. import LOG as _LOG -from ..binarywave import TYPE_TABLE as _TYPE_TABLE -from ..binarywave import NullStaticStringField as _NullStaticStringField -from ..binarywave import DynamicStringField as _DynamicStringField -from ..struct import Structure as _Structure -from ..struct import DynamicStructure as _DynamicStructure -from ..struct import Field as _Field -from ..struct import DynamicField as _DynamicField -from ..util import byte_order as _byte_order -from ..util import need_to_reorder_bytes as _need_to_reorder_bytes -from .base import Record - - -class ListedStaticStringField (_NullStaticStringField): - """Handle string conversions for multi-count dynamic parents. - - If a field belongs to a multi-count dynamic parent, the parent is - called multiple times to parse each count, and the field's - post-unpack hook gets called after the field is unpacked during - each iteration. This requires alternative logic for getting and - setting the string data. The actual string formatting code is not - affected. - """ - def post_unpack(self, parents, data): - parent_structure = parents[-1] - parent_data = self._get_structure_data(parents, data, parent_structure) - d = self._normalize_string(parent_data[-1][self.name]) - parent_data[-1][self.name] = d - - -class ListedStaticStringField (_NullStaticStringField): - """Handle string conversions for multi-count dynamic parents. - - If a field belongs to a multi-count dynamic parent, the parent is - called multiple times to parse each count, and the field's - post-unpack hook gets called after the field is unpacked during - each iteration. This requires alternative logic for getting and - setting the string data. The actual string formatting code is not - affected. - """ - def post_unpack(self, parents, data): - parent_structure = parents[-1] - parent_data = self._get_structure_data(parents, data, parent_structure) - d = self._normalize_string(parent_data[-1][self.name]) - parent_data[-1][self.name] = d - - -class ListedDynamicStrDataField (_DynamicStringField, ListedStaticStringField): - _size_field = 'strLen' - _null_terminated = False - - def _get_size_data(self, parents, data): - parent_structure = parents[-1] - parent_data = self._get_structure_data(parents, data, parent_structure) - return parent_data[-1][self._size_field] - - -class DynamicVarDataField (_DynamicField): - def __init__(self, *args, **kwargs): - if 'array' not in kwargs: - kwargs['array'] = True - super(DynamicVarDataField, self).__init__(*args, **kwargs) - - def pre_pack(self, parents, data): - raise NotImplementedError() - - def post_unpack(self, parents, data): - var_structure = parents[-1] - var_data = self._get_structure_data(parents, data, var_structure) - data = var_data[self.name] - d = {} - for i,value in enumerate(data): - key,value = self._normalize_item(i, value) - d[key] = value - var_data[self.name] = d - - def _normalize_item(self, index, value): - raise NotImplementedError() - - -class DynamicSysVarField (DynamicVarDataField): - def _normalize_item(self, index, value): - name = 'K{}'.format(index) - return (name, value) - - -class DynamicUserVarField (DynamicVarDataField): - def _normalize_item(self, index, value): - name = value['name'] - value = value['num'] - return (name, value) - - -class DynamicUserStrField (DynamicVarDataField): - def _normalize_item(self, index, value): - name = value['name'] - value = value['data'] - return (name, value) - - -class DynamicVarNumField (_DynamicField): - def post_unpack(self, parents, data): - parent_structure = parents[-1] - parent_data = self._get_structure_data(parents, data, parent_structure) - d = self._normalize_numeric_variable(parent_data[-1][self.name]) - parent_data[-1][self.name] = d - - def _normalize_numeric_variable(self, num_var): - t = _TYPE_TABLE[num_var['numType']] - if num_var['numType'] % 2: # complex number - return t(complex(num_var['realPart'], num_var['imagPart'])) - else: - return t(num_var['realPart']) - - -class DynamicFormulaField (_DynamicStringField): - _size_field = 'formulaLen' - _null_terminated = True - - -# From Variables.h -VarHeader1 = _Structure( # `version` field pulled out into VariablesRecord - name='VarHeader1', - fields=[ - _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'), - _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'), - _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'), - ]) - -# From Variables.h -VarHeader2 = _Structure( # `version` field pulled out into VariablesRecord - name='VarHeader2', - fields=[ - _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'), - _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'), - _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'), - _Field('h', 'numDependentVars', help='Number of dependent numeric variables -- may be zero.'), - _Field('h', 'numDependentStrs', help='Number of dependent string variables -- may be zero.'), - ]) - -# From Variables.h -UserStrVarRec1 = _DynamicStructure( - name='UserStrVarRec1', - fields=[ - ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), - _Field('h', 'strLen', help='The real size of the following array.'), - ListedDynamicStrDataField('c', 'data'), - ]) - -# From Variables.h -UserStrVarRec2 = _DynamicStructure( - name='UserStrVarRec2', - fields=[ - ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), - _Field('l', 'strLen', help='The real size of the following array.'), - _Field('c', 'data'), - ]) - -# From Variables.h -VarNumRec = _Structure( - name='VarNumRec', - fields=[ - _Field('h', 'numType', help='Type from binarywave.TYPE_TABLE'), - _Field('d', 'realPart', help='The real part of the number.'), - _Field('d', 'imagPart', help='The imag part if the number is complex.'), - _Field('l', 'reserved', help='Reserved - set to zero.'), - ]) - -# From Variables.h -UserNumVarRec = _DynamicStructure( - name='UserNumVarRec', - fields=[ - ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), - _Field('h', 'type', help='0 = string, 1 = numeric.'), - DynamicVarNumField(VarNumRec, 'num', help='Type and value of the variable if it is numeric. Not used for string.'), - ]) - -# From Variables.h -UserDependentVarRec = _DynamicStructure( - name='UserDependentVarRec', - fields=[ - ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), - _Field('h', 'type', help='0 = string, 1 = numeric.'), - _Field(VarNumRec, 'num', help='Type and value of the variable if it is numeric. Not used for string.'), - _Field('h', 'formulaLen', help='The length of the dependency formula.'), - DynamicFormulaField('c', 'formula', help='Start of the dependency formula. A C string including null terminator.'), - ]) - - -class DynamicVarHeaderField (_DynamicField): - def pre_pack(self, parents, data): - raise NotImplementedError() - - def post_unpack(self, parents, data): - var_structure = parents[-1] - var_data = self._get_structure_data( - parents, data, var_structure) - var_header_structure = self.format - data = var_data['var_header'] - sys_vars_field = var_structure.get_field('sysVars') - sys_vars_field.count = data['numSysVars'] - sys_vars_field.setup() - user_vars_field = var_structure.get_field('userVars') - user_vars_field.count = data['numUserVars'] - user_vars_field.setup() - user_strs_field = var_structure.get_field('userStrs') - user_strs_field.count = data['numUserStrs'] - user_strs_field.setup() - if 'numDependentVars' in data: - dependent_vars_field = var_structure.get_field('dependentVars') - dependent_vars_field.count = data['numDependentVars'] - dependent_vars_field.setup() - dependent_strs_field = var_structure.get_field('dependentStrs') - dependent_strs_field.count = data['numDependentStrs'] - dependent_strs_field.setup() - var_structure.setup() - - -Variables1 = _DynamicStructure( - name='Variables1', - fields=[ - DynamicVarHeaderField(VarHeader1, 'var_header', help='Variables header'), - DynamicSysVarField('f', 'sysVars', help='System variables', count=0), - DynamicUserVarField(UserNumVarRec, 'userVars', help='User numeric variables', count=0), - DynamicUserStrField(UserStrVarRec1, 'userStrs', help='User string variables', count=0), - ]) - - -Variables2 = _DynamicStructure( - name='Variables2', - fields=[ - DynamicVarHeaderField(VarHeader2, 'var_header', help='Variables header'), - DynamicSysVarField('f', 'sysVars', help='System variables', count=0), - DynamicUserVarField(UserNumVarRec, 'userVars', help='User numeric variables', count=0), - DynamicUserStrField(UserStrVarRec2, 'userStrs', help='User string variables', count=0), - _Field(UserDependentVarRec, 'dependentVars', help='Dependent numeric variables.', count=0, array=True), - _Field(UserDependentVarRec, 'dependentStrs', help='Dependent string variables.', count=0, array=True), - ]) - - -class DynamicVersionField (_DynamicField): - def pre_pack(self, parents, byte_order): - raise NotImplementedError() - - def post_unpack(self, parents, data): - variables_structure = parents[-1] - variables_data = self._get_structure_data( - parents, data, variables_structure) - version = variables_data['version'] - if variables_structure.byte_order in '@=': - need_to_reorder_bytes = _need_to_reorder_bytes(version) - variables_structure.byte_order = _byte_order(need_to_reorder_bytes) - _LOG.debug( - 'get byte order from version: {} (reorder? {})'.format( - variables_structure.byte_order, need_to_reorder_bytes)) - else: - need_to_reorder_bytes = False - - old_format = variables_structure.fields[-1].format - if version == 1: - variables_structure.fields[-1].format = Variables1 - elif version == 2: - variables_structure.fields[-1].format = Variables2 - elif not need_to_reorder_bytes: - raise ValueError( - 'invalid variables record version: {}'.format(version)) - - if variables_structure.fields[-1].format != old_format: - _LOG.debug('change variables record from {} to {}'.format( - old_format, variables_structure.fields[-1].format)) - variables_structure.setup() - elif need_to_reorder_bytes: - variables_structure.setup() - - # we might need to unpack again with the new byte order - return need_to_reorder_bytes - - -VariablesRecordStructure = _DynamicStructure( - name='VariablesRecord', - fields=[ - DynamicVersionField('h', 'version', help='Version number for this header.'), - _Field(Variables1, 'variables', help='The rest of the variables data.'), - ]) - - -class VariablesRecord (Record): - def __init__(self, *args, **kwargs): - super(VariablesRecord, self).__init__(*args, **kwargs) - # self.header['version'] # record version always 0? - VariablesRecordStructure.byte_order = '=' - VariablesRecordStructure.setup() - stream = _io.BytesIO(bytes(self.data)) - self.variables = VariablesRecordStructure.unpack_stream(stream) - self.namespace = {} - for key,value in self.variables['variables'].items(): - if key not in ['var_header']: - _LOG.debug('update namespace {} with {} for {}'.format( - self.namespace, value, key)) - self.namespace.update(value) diff --git a/igor/script.py b/igor/script.py deleted file mode 100644 index 83fde93..0000000 --- a/igor/script.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (C) 2012-2016 W. Trevor King -# -# This file is part of igor. -# -# igor is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# igor is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . - -"Common code for scripts distributed with the `igor` package." - -from __future__ import absolute_import -import argparse as _argparse -import logging as _logging -import sys as _sys - -try: - import matplotlib as _matplotlib - import matplotlib.pyplot as _matplotlib_pyplot -except ImportError as _matplotlib_import_error: - _matplotlib = None - -from . import __version__ -from . import LOG as _LOG - - -class Script (object): - log_levels = [_logging.ERROR, _logging.WARNING, _logging.INFO, _logging.DEBUG] - - def __init__(self, description=None, filetype='IGOR Binary Wave (.ibw) file'): - self.parser = _argparse.ArgumentParser(description=description) - self.parser.add_argument( - '--version', action='version', - version='%(prog)s {}'.format(__version__)) - self.parser.add_argument( - '-f', '--infile', metavar='FILE', default='-', - help='input {}'.format(filetype)) - self.parser.add_argument( - '-o', '--outfile', metavar='FILE', default='-', - help='file for ASCII output') - self.parser.add_argument( - '-p', '--plot', action='store_const', const=True, - help='use Matplotlib to plot any IGOR waves') - self.parser.add_argument( - '-V', '--verbose', action='count', default=0, - help='increment verbosity') - self._num_plots = 0 - - def run(self, *args, **kwargs): - args = self.parser.parse_args(*args, **kwargs) - if args.infile == '-': - args.infile = _sys.stdin - if args.outfile == '-': - args.outfile = _sys.stdout - if args.verbose > 1: - log_level = self.log_levels[min(args.verbose-1, len(self.log_levels)-1)] - _LOG.setLevel(log_level) - self._run(args) - self.display_plots() - - def _run(self, args): - raise NotImplementedError() - - def plot_wave(self, args, wave, title=None): - if not args.plot: - return # no-op - if not _matplotlib: - raise _matplotlib_import_error - if title is None: - title = wave['wave']['wave_header']['bname'] - figure = _matplotlib_pyplot.figure() - axes = figure.add_subplot(1, 1, 1) - axes.set_title(title) - try: - axes.plot(wave['wave']['wData'], 'r.') - except ValueError as error: - _LOG.error('error plotting {}: {}'.format(title, error)) - pass - self._num_plots += 1 - - def display_plots(self): - if self._num_plots: - _matplotlib_pyplot.show() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..adbb2db --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[project] +name = "pygor" +version = "1.0.0" +description = "Package for loading binary IGOR binary" +readme = "README.md" +authors = [ + { name = "W. Trevor King", email = 'wking@tremily.us'}, + { name = "Jacob Gobbo", email = "gobbo.jacob@gmail.com" } +] +requires-python = ">=3.12" +dependencies = [ + "numpy>=2.0.0", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[dependency-groups] +dev = [ + "pytest>=8.4.1", +] diff --git a/setup.py b/setup.py deleted file mode 100644 index 0f3a04a..0000000 --- a/setup.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (C) 2011-2016 Paul Kienzle -# W. Trevor King -# -# This file is part of igor. -# -# igor is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# igor is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . - -"igor: interface for reading binary IGOR files." - -from distutils.core import setup -import os.path - -from igor import __version__ - - -package_name = 'igor' -_this_dir = os.path.dirname(__file__) - -setup(name=package_name, - version=__version__, - author='W. Trevor King', - author_email='wking@tremily.us', - maintainer='Conrad Stansbury', - maintainer_email='chstan@berkeley.edu', - url='https://github.com/chstan/igorpy', - download_url='https://github.com/chstan/igorpy/tarball/master', - license='GNU Lesser General Public License v3 or later (LGPLv3+)', - platforms=['all'], - description=__doc__, - long_description=open(os.path.join(_this_dir, 'README'), 'r').read(), - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Operating System :: OS Independent', - 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Topic :: Scientific/Engineering', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - packages=[ - 'igor', - 'igor.record', - ], - scripts=[ - 'bin/igorbinarywave.py', - 'bin/igorpackedexperiment.py', - ], - provides=['igor ({})'.format(__version__)], - ) diff --git a/igor/__init__.py b/src/pygor/__init__.py similarity index 51% rename from igor/__init__.py rename to src/pygor/__init__.py index 7f0220a..a37918d 100644 --- a/igor/__init__.py +++ b/src/pygor/__init__.py @@ -1,30 +1,20 @@ # Copyright (C) 2012-2016 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . "Interface for reading binary IGOR files." -__version__ = '0.3.1' - - -import logging as _logging - - -LOG = _logging.getLogger('igor') -LOG.setLevel(_logging.ERROR) -LOG.addHandler(_logging.StreamHandler()) -LOG.handlers[-1].setFormatter( - _logging.Formatter('%(name)s - %(levelname)s - %(message)s')) +from .core import * diff --git a/src/pygor/binarywave.py b/src/pygor/binarywave.py new file mode 100644 index 0000000..432d99c --- /dev/null +++ b/src/pygor/binarywave.py @@ -0,0 +1,970 @@ +# Copyright (C) 2010-2012 W. Trevor King +# +# This file is part of pygor. +# +# pygor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with pygor. If not, see . + +"Read IGOR Binary Wave files into Numpy arrays." + +# Based on WaveMetric's Technical Note 003, "Igor Binary Format" +# ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip +# From ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN000.txt +# We place no restrictions on copying Technical Notes, with the +# exception that you cannot resell them. So read, enjoy, and +# share. We hope IGOR Technical Notes will provide you with lots of +# valuable information while you are developing IGOR applications. + +import numpy as np + +from .struct import Structure, DynamicStructure, Field, DynamicField +from .util import byte_order, need_to_reorder_bytes, checksum + + +# Numpy doesn't support complex integers by default, see +# http://mail.python.org/pipermail/python-dev/2002-April/022408.html +# http://mail.scipy.org/pipermail/numpy-discussion/2007-October/029447.html +# So we roll our own types. See +# http://docs.scipy.org/doc/numpy/user/basics.rec.html +# http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html +complexInt8 = np.dtype([("real", np.int8), ("imag", np.int8)]) +complexInt16 = np.dtype([("real", np.int16), ("imag", np.int16)]) +complexInt32 = np.dtype([("real", np.int32), ("imag", np.int32)]) +complexUInt8 = np.dtype([("real", np.uint8), ("imag", np.uint8)]) +complexUInt16 = np.dtype([("real", np.uint16), ("imag", np.uint16)]) +complexUInt32 = np.dtype([("real", np.uint32), ("imag", np.uint32)]) + + +class StaticStringField(DynamicField): + _null_terminated = False + _array_size_field = None + + def __init__(self, *args, **kwargs): + if "array" not in kwargs: + kwargs["array"] = True + super(StaticStringField, self).__init__(*args, **kwargs) + + def post_unpack(self, parents, data): + wave_structure = parents[-1] + wave_data = self._get_structure_data(parents, data, wave_structure) + d = self._normalize_string(wave_data[self.name]) + wave_data[self.name] = d + + def _normalize_string(self, d): + if isinstance(d, bytes): + pass + elif hasattr(d, "tobytes"): + d = d.tobytes() + elif hasattr(d, "tostring"): # Python 2 compatibility + d = d.tostring() + else: + d = b"".join(d) + if self._array_size_field: + start = 0 + strings = [] + for count in self.counts: + end = start + count + if end > start: + strings.append(d[start:end]) + if self._null_terminated: + strings[-1] = strings[-1].split(b"\x00", 1)[0] + start = end + elif self._null_terminated: + d = d.split(b"\x00", 1)[0] + return d + + +class NullStaticStringField(StaticStringField): + _null_terminated = True + + +# Begin IGOR constants and typedefs from IgorBin.h + +# From IgorMath.h +TYPE_TABLE = { # (key: integer flag, value: numpy dtype) + 0: None, # Text wave, not handled in ReadWave.c + 1: np.complex128, # NT_CMPLX, makes number complex. + 2: np.float32, # NT_FP32, 32 bit fp numbers. + 3: np.complex64, + 4: np.float64, # NT_FP64, 64 bit fp numbers. + 5: np.complex128, + 8: np.int8, # NT_I8, 8 bit signed integer. Requires Igor Pro + # 2.0 or later. + 9: complexInt8, + 0x10: np.int16, # NT_I16, 16 bit integer numbers. Requires Igor + # Pro 2.0 or later. + 0x11: complexInt16, + 0x20: np.int32, # NT_I32, 32 bit integer numbers. Requires Igor + # Pro 2.0 or later. + 0x21: complexInt32, + # 0x40:None, # NT_UNSIGNED, Makes above signed integers + # # unsigned. Requires Igor Pro 3.0 or later. + 0x48: np.uint8, + 0x49: complexUInt8, + 0x50: np.uint16, + 0x51: complexUInt16, + 0x60: np.uint32, + 0x61: complexUInt32, +} + +# From wave.h +MAXDIMS = 4 + +# From binary.h +BinHeader1 = Structure( # `version` field pulled out into Wave + name="BinHeader1", + fields=[ + Field( + "l", + "wfmSize", + help="The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.", + ), + Field("h", "checksum", help="Checksum over this header and the wave header."), + ], +) + +BinHeader2 = Structure( # `version` field pulled out into Wave + name="BinHeader2", + fields=[ + Field( + "l", + "wfmSize", + help="The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.", + ), + Field("l", "noteSize", help="The size of the note text."), + Field("l", "pictSize", default=0, help="Reserved. Write zero. Ignore on read."), + Field("h", "checksum", help="Checksum over this header and the wave header."), + ], +) + +BinHeader3 = Structure( # `version` field pulled out into Wave + name="BinHeader3", + fields=[ + Field( + "l", + "wfmSize", + help="The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.", + ), + Field("l", "noteSize", help="The size of the note text."), + Field("l", "formulaSize", help="The size of the dependency formula, if any."), + Field("l", "pictSize", default=0, help="Reserved. Write zero. Ignore on read."), + Field("h", "checksum", help="Checksum over this header and the wave header."), + ], +) + +BinHeader5 = Structure( # `version` field pulled out into Wave + name="BinHeader5", + fields=[ + Field("h", "checksum", help="Checksum over this header and the wave header."), + Field( + "l", + "wfmSize", + help="The size of the WaveHeader5 data structure plus the wave data.", + ), + Field("l", "formulaSize", help="The size of the dependency formula, if any."), + Field("l", "noteSize", help="The size of the note text."), + Field("l", "dataEUnitsSize", help="The size of optional extended data units."), + Field( + "l", + "dimEUnitsSize", + help="The size of optional extended dimension units.", + count=MAXDIMS, + array=True, + ), + Field( + "l", + "dimLabelsSize", + help="The size of optional dimension labels.", + count=MAXDIMS, + array=True, + ), + Field( + "l", + "sIndicesSize", + help="The size of string indicies if this is a text wave.", + ), + Field( + "l", "optionsSize1", default=0, help="Reserved. Write zero. Ignore on read." + ), + Field( + "l", "optionsSize2", default=0, help="Reserved. Write zero. Ignore on read." + ), + ], +) + + +# From wave.h +MAX_WAVE_NAME2 = 18 # Maximum length of wave name in version 1 and 2 +# files. Does not include the trailing null. +MAX_WAVE_NAME5 = 31 # Maximum length of wave name in version 5 +# files. Does not include the trailing null. +MAX_UNIT_CHARS = 3 + +# Header to an array of waveform data. + +# `wData` field pulled out into DynamicWaveDataField1 +WaveHeader2 = DynamicStructure( + name="WaveHeader2", + fields=[ + Field("h", "type", help="See types (e.g. NT_FP64) above. Zero for text waves."), + Field( + "P", + "next", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + NullStaticStringField( + "c", + "bname", + help="Name of wave plus trailing null.", + count=MAX_WAVE_NAME2 + 2, + ), + Field("h", "whVersion", default=0, help="Write 0. Ignore on read."), + Field( + "h", + "srcFldr", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "P", + "fileName", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "c", + "dataUnits", + default=0, + help="Natural data units go here - null if none.", + count=MAX_UNIT_CHARS + 1, + array=True, + ), + Field( + "c", + "xUnits", + default=0, + help="Natural x-axis units go here - null if none.", + count=MAX_UNIT_CHARS + 1, + array=True, + ), + Field("l", "npnts", help="Number of data points in wave."), + Field( + "h", + "aModified", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field("d", "hsA", help="X value for point p = hsA*p + hsB"), + Field("d", "hsB", help="X value for point p = hsA*p + hsB"), + Field( + "h", + "wModified", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "h", + "swModified", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field("h", "fsValid", help="True if full scale values have meaning."), + Field( + "d", "topFullScale", help="The min full scale value for wave." + ), # sic, 'min' should probably be 'max' + Field("d", "botFullScale", help="The min full scale value for wave."), + Field( + "c", + "useBits", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field("c", "kindBits", default=0, help="Reserved. Write zero. Ignore on read."), + Field( + "P", + "formula", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "l", + "depID", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "L", + "creationDate", + help="DateTime of creation. Not used in version 1 files.", + ), + Field( + "c", + "wUnused", + default=0, + help="Reserved. Write zero. Ignore on read.", + count=2, + array=True, + ), + Field("L", "modDate", help="DateTime of last modification."), + Field( + "P", "waveNoteH", help="Used in memory only. Write zero. Ignore on read." + ), + ], +) + +# `sIndices` pointer unset (use Wave5_data['sIndices'] instead). This +# field is filled in by DynamicStringIndicesDataField. +# `wData` field pulled out into DynamicWaveDataField5 +WaveHeader5 = DynamicStructure( + name="WaveHeader5", + fields=[ + Field("P", "next", help="link to next wave in linked list."), + Field("L", "creationDate", help="DateTime of creation."), + Field("L", "modDate", help="DateTime of last modification."), + Field( + "l", + "npnts", + help="Total number of points (multiply dimensions up to first zero).", + ), + Field("h", "type", help="See types (e.g. NT_FP64) above. Zero for text waves."), + Field("h", "dLock", default=0, help="Reserved. Write zero. Ignore on read."), + Field( + "c", + "whpad1", + default=0, + help="Reserved. Write zero. Ignore on read.", + count=6, + array=True, + ), + Field("h", "whVersion", default=1, help="Write 1. Ignore on read."), + NullStaticStringField( + "c", + "bname", + help="Name of wave plus trailing null.", + count=MAX_WAVE_NAME5 + 1, + ), + Field("l", "whpad2", default=0, help="Reserved. Write zero. Ignore on read."), + Field( + "P", + "dFolder", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + # Dimensioning info. [0] == rows, [1] == cols etc + Field( + "l", + "nDim", + help="Number of of items in a dimension -- 0 means no data.", + count=MAXDIMS, + array=True, + ), + Field( + "d", + "sfA", + help="Index value for element e of dimension d = sfA[d]*e + sfB[d].", + count=MAXDIMS, + array=True, + ), + Field( + "d", + "sfB", + help="Index value for element e of dimension d = sfA[d]*e + sfB[d].", + count=MAXDIMS, + array=True, + ), + # SI units + Field( + "c", + "dataUnits", + default=0, + help="Natural data units go here - null if none.", + count=MAX_UNIT_CHARS + 1, + array=True, + ), + Field( + "c", + "dimUnits", + default=0, + help="Natural dimension units go here - null if none.", + count=(MAXDIMS, MAX_UNIT_CHARS + 1), + array=True, + ), + Field("h", "fsValid", help="TRUE if full scale values have meaning."), + Field("h", "whpad3", default=0, help="Reserved. Write zero. Ignore on read."), + Field( + "d", "topFullScale", help="The max and max full scale value for wave" + ), # sic, probably "max and min" + Field( + "d", "botFullScale", help="The max and max full scale value for wave." + ), # sic, probably "max and min" + Field( + "P", + "dataEUnits", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "P", + "dimEUnits", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + count=MAXDIMS, + array=True, + ), + Field( + "P", + "dimLabels", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + count=MAXDIMS, + array=True, + ), + Field( + "P", + "waveNoteH", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "l", + "whUnused", + default=0, + help="Reserved. Write zero. Ignore on read.", + count=16, + array=True, + ), + # The following stuff is considered private to Igor. + Field( + "h", + "aModified", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "h", + "wModified", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "h", + "swModified", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "c", + "useBits", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field("c", "kindBits", default=0, help="Reserved. Write zero. Ignore on read."), + Field( + "P", + "formula", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "l", + "depID", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field("h", "whpad4", default=0, help="Reserved. Write zero. Ignore on read."), + Field( + "h", + "srcFldr", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "P", + "fileName", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + Field( + "P", + "sIndices", + default=0, + help="Used in memory only. Write zero. Ignore on read.", + ), + ], +) + + +class DynamicWaveDataField1(DynamicField): + def pre_pack(self, parents, data): + raise NotImplementedError() + + def pre_unpack(self, parents, data): + full_structure = parents[0] + wave_structure = parents[-1] + wave_header_structure = wave_structure.fields[1].format + wave_data = self._get_structure_data(parents, data, wave_structure) + version = data["version"] + bin_header = wave_data["bin_header"] + wave_header = wave_data["wave_header"] + + self.count = wave_header["npnts"] + self.data_size = self._get_size(bin_header, wave_header_structure.size) + + type_ = TYPE_TABLE.get(wave_header["type"], None) + if type_: + self.shape = self._get_shape(bin_header, wave_header) + else: # text wave + type_ = np.dtype("S1") + self.shape = (self.data_size,) + # dtype() wrapping to avoid numpy.generic and + # getset_descriptor issues with the builtin numpy types + # (e.g. int32). It has no effect on our local complex + # integers. + self.dtype = np.dtype(type_).newbyteorder(wave_structure.byte_order) + if ( + version == 3 + and self.count > 0 + and bin_header["formulaSize"] > 0 + and self.data_size == 0 + ): + """From TN003: + + Igor Pro 2.00 included support for dependency formulae. If + a wave was governed by a dependency formula then the + actual wave data was not written to disk for that wave, + because on loading the wave Igor could recalculate the + data. However,this prevented the wave from being loaded + into an experiment other than the original + experiment. Consequently, in a version of Igor Pro 3.0x, + we changed it so that the wave data was written even if + the wave was governed by a dependency formula. When + reading a binary wave file, you can detect that the wave + file does not contain the wave data by examining the + wfmSize, formulaSize and npnts fields. If npnts is greater + than zero and formulaSize is greater than zero and + the waveDataSize as calculated above is zero, then this is + a file governed by a dependency formula that was written + without the actual wave data. + """ + self.shape = (0,) + elif TYPE_TABLE.get(wave_header["type"], None) is not None: + assert self.data_size == self.count * self.dtype.itemsize, ( + self.data_size, + self.count, + self.dtype.itemsize, + self.dtype, + ) + else: + assert self.data_size >= 0, ( + bin_header["wfmSize"], + wave_header_structure.size, + ) + + def _get_size(self, bin_header, wave_header_size): + return bin_header["wfmSize"] - wave_header_size - 16 + + def _get_shape(self, bin_header, wave_header): + return (self.count,) + + def unpack(self, stream): + data_b = stream.read(self.data_size) + data = np.ndarray( + shape=self.shape, + dtype=self.dtype, + buffer=data_b, + order="F", + ) + return data + + +class DynamicWaveDataField5(DynamicWaveDataField1): + "Adds support for multidimensional data." + + def _get_size(self, bin_header, wave_header_size): + return bin_header["wfmSize"] - wave_header_size + + def _get_shape(self, bin_header, wave_header): + return [n for n in wave_header["nDim"] if n > 0] or (0,) + + +# End IGOR constants and typedefs from IgorBin.h + + +class DynamicStringField(StaticStringField): + _size_field = None + + def pre_unpack(self, parents, data): + size = self._get_size_data(parents, data) + if self._array_size_field: + self.counts = size + self.count = sum(self.counts) + else: + self.count = size + self.setup() + + def _get_size_data(self, parents, data): + wave_structure = parents[-1] + wave_data = self._get_structure_data(parents, data, wave_structure) + bin_header = wave_data["bin_header"] + return bin_header[self._size_field] + + +class DynamicWaveNoteField(DynamicStringField): + _size_field = "noteSize" + + +class DynamicDependencyFormulaField(DynamicStringField): + """Optional wave dependency formula + + Excerpted from TN003: + + A wave has a dependency formula if it has been bound by a + statement such as "wave0 := sin(x)". In this example, the + dependency formula is "sin(x)". The formula is stored with + no trailing null byte. + """ + + _size_field = "formulaSize" + # Except when it is stored with a trailing null byte :p. See, for + # example, test/data/mac-version3Dependent.ibw. + _null_terminated = True + + +class DynamicDataUnitsField(DynamicStringField): + """Optional extended data units data + + Excerpted from TN003: + + dataUnits - Present in versions 1, 2, 3, 5. The dataUnits field + stores the units for the data represented by the wave. It is a C + string terminated with a null character. This field supports + units of 0 to 3 bytes. In version 1, 2 and 3 files, longer units + can not be represented. In version 5 files, longer units can be + stored using the optional extended data units section of the + file. + """ + + _size_field = "dataEUnitsSize" + + +class DynamicDimensionUnitsField(DynamicStringField): + """Optional extended dimension units data + + Excerpted from TN003: + + xUnits - Present in versions 1, 2, 3. The xUnits field stores the + X units for a wave. It is a C string terminated with a null + character. This field supports units of 0 to 3 bytes. In + version 1, 2 and 3 files, longer units can not be represented. + + dimUnits - Present in version 5 only. This field is an array of 4 + strings, one for each possible wave dimension. Each string + supports units of 0 to 3 bytes. Longer units can be stored using + the optional extended dimension units section of the file. + """ + + _size_field = "dimEUnitsSize" + _array_size_field = True + + +class DynamicLabelsField(DynamicStringField): + """Optional dimension label data + + From TN003: + + If the wave has dimension labels for dimension d then the + dimLabelsSize[d] field of the BinHeader5 structure will be + non-zero. + + A wave will have dimension labels if a SetDimLabel command has + been executed on it. + + A 3 point 1D wave has 4 dimension labels. The first dimension + label is the label for the dimension as a whole. The next three + dimension labels are the labels for rows 0, 1, and 2. When Igor + writes dimension labels to disk, it writes each dimension label as + a C string (null-terminated) in a field of 32 bytes. + """ + + _size_field = "dimLabelsSize" + _array_size_field = True + + def post_unpack(self, parents, data): + wave_structure = parents[-1] + wave_data = self._get_structure_data(parents, data, wave_structure) + bin_header = wave_data["bin_header"] + d = wave_data[self.name] + dim_labels = [] + start = 0 + for size in bin_header[self._size_field]: + end = start + size + if end > start: + dim_data = d[start:end] + chunks = [] + for i in range(size // 32): + chunks.append(dim_data[32 * i : 32 * (i + 1)]) + labels = [b""] + for chunk in chunks: + labels[-1] = labels[-1] + b"".join(chunk) + if b"\x00" in chunk: + labels.append(b"") + labels.pop(-1) + start = end + else: + labels = [] + dim_labels.append(labels) + wave_data[self.name] = dim_labels + + +class DynamicStringIndicesDataField(DynamicField): + """String indices used for text waves only""" + + def pre_pack(self, parents, data): + raise NotImplementedError() + + def pre_unpack(self, parents, data): + wave_structure = parents[-1] + wave_data = self._get_structure_data(parents, data, wave_structure) + bin_header = wave_data["bin_header"] + wave_header = wave_data["wave_header"] + self.string_indices_size = bin_header["sIndicesSize"] + self.count = self.string_indices_size // 4 + if self.count: # make sure we're in a text wave + assert TYPE_TABLE[wave_header["type"]] is None, wave_header + self.setup() + + def post_unpack(self, parents, data): + if not self.count: + return + wave_structure = parents[-1] + wave_data = self._get_structure_data(parents, data, wave_structure) + wave_header = wave_data["wave_header"] + wdata = wave_data["wData"] + strings = [] + start = 0 + for i, offset in enumerate(wave_data["sIndices"]): + if offset > start: + chars = wdata[start:offset] + strings.append(b"".join(chars)) + start = offset + elif offset == start: + strings.append(b"") + else: + raise ValueError((offset, wave_data["sIndices"])) + wdata = np.array(strings) + shape = [n for n in wave_header["nDim"] if n > 0] or (0,) + wdata = wdata.reshape(shape) + wave_data["wData"] = wdata + + +class DynamicVersionField(DynamicField): + def pre_pack(self, parents, byte_order): + raise NotImplementedError() + + def post_unpack(self, parents, data): + wave_structure = parents[-1] + wave_data = self._get_structure_data(parents, data, wave_structure) + version = wave_data["version"] + if wave_structure.byte_order in "@=": + should_reorder_bytes = need_to_reorder_bytes(version) + wave_structure.byte_order = byte_order(should_reorder_bytes) + else: + should_reorder_bytes = False + + old_format = wave_structure.fields[-1].format + if version == 1: + wave_structure.fields[-1].format = Wave1 + elif version == 2: + wave_structure.fields[-1].format = Wave2 + elif version == 3: + wave_structure.fields[-1].format = Wave3 + elif version == 5: + wave_structure.fields[-1].format = Wave5 + elif not should_reorder_bytes: + raise ValueError("invalid binary wave version: {}".format(version)) + + if wave_structure.fields[-1].format != old_format: + wave_structure.setup() + elif should_reorder_bytes: + wave_structure.setup() + + # we might need to unpack again with the new byte order + return should_reorder_bytes + + +class DynamicWaveField(DynamicField): + def post_unpack(self, parents, data): + return + raise NotImplementedError() # TODO + checksum_size = bin.size + wave.size + wave_structure = parents[-1] + if version == 5: + # Version 5 checksum does not include the wData field. + checksum_size -= 4 + c = checksum(b, parents[-1].byte_order, 0, checksum_size) + if c != 0: + raise ValueError( + ( + "This does not appear to be a valid Igor binary wave file. " + "Error in checksum: should be 0, is {}." + ).format(c) + ) + + +Wave1 = DynamicStructure( + name="Wave1", + fields=[ + Field(BinHeader1, "bin_header", help="Binary wave header"), + Field(WaveHeader2, "wave_header", help="Wave header"), + DynamicWaveDataField1( + "f", + "wData", + help="The start of the array of waveform data.", + count=0, + array=True, + ), + ], +) + +Wave2 = DynamicStructure( + name="Wave2", + fields=[ + Field(BinHeader2, "bin_header", help="Binary wave header"), + Field(WaveHeader2, "wave_header", help="Wave header"), + DynamicWaveDataField1( + "f", + "wData", + help="The start of the array of waveform data.", + count=0, + array=True, + ), + Field( + "x", + "padding", + help="16 bytes of padding in versions 2 and 3.", + count=16, + array=True, + ), + DynamicWaveNoteField( + "c", "note", help="Optional wave note data", count=0, array=True + ), + ], +) + +Wave3 = DynamicStructure( + name="Wave3", + fields=[ + Field(BinHeader3, "bin_header", help="Binary wave header"), + Field(WaveHeader2, "wave_header", help="Wave header"), + DynamicWaveDataField1( + "f", + "wData", + help="The start of the array of waveform data.", + count=0, + array=True, + ), + Field( + "x", + "padding", + help="16 bytes of padding in versions 2 and 3.", + count=16, + array=True, + ), + DynamicWaveNoteField( + "c", "note", help="Optional wave note data", count=0, array=True + ), + DynamicDependencyFormulaField( + "c", "formula", help="Optional wave dependency formula", count=0, array=True + ), + ], +) + +Wave5 = DynamicStructure( + name="Wave5", + fields=[ + Field(BinHeader5, "bin_header", help="Binary wave header"), + Field(WaveHeader5, "wave_header", help="Wave header"), + DynamicWaveDataField5( + "f", + "wData", + help="The start of the array of waveform data.", + count=0, + array=True, + ), + DynamicDependencyFormulaField( + "c", + "formula", + help="Optional wave dependency formula.", + count=0, + array=True, + ), + DynamicWaveNoteField( + "c", "note", help="Optional wave note data.", count=0, array=True + ), + DynamicDataUnitsField( + "c", + "data_units", + help="Optional extended data units data.", + count=0, + array=True, + ), + DynamicDimensionUnitsField( + "c", + "dimension_units", + help="Optional dimension label data", + count=0, + array=True, + ), + DynamicLabelsField( + "c", "labels", help="Optional dimension label data", count=0, array=True + ), + DynamicStringIndicesDataField( + "P", + "sIndices", + help="Dynamic string indices for text waves.", + count=0, + array=True, + ), + ], +) + +Wave = DynamicStructure( + name="Wave", + fields=[ + DynamicVersionField( + "h", "version", help="Version number for backwards compatibility." + ), + DynamicWaveField(Wave1, "wave", help="The rest of the wave data."), + ], +) + + +def load_ibw(filename): + if hasattr(filename, "read"): + f = filename # filename is actually a stream object + else: + f = open(filename, "rb") + try: + Wave.byte_order = "=" + Wave.setup() + data = Wave.unpack_stream(f) + finally: + if not hasattr(filename, "read"): + f.close() + + return data + + +def save(filename): + raise NotImplementedError diff --git a/src/pygor/core.py b/src/pygor/core.py new file mode 100644 index 0000000..20c753f --- /dev/null +++ b/src/pygor/core.py @@ -0,0 +1,352 @@ +# This program is in the public domain +"""`pygor.py` compatibility layer on top of the `pygor` package. + +pygor.load('filename') or pygor.loads('data') loads the content of an igore file +into memory as a folder structure. + +Returns the root folder. + +Folders have name, path and children. +Children can be indexed by folder[i] or by folder['name']. +To see the whole tree, use: print folder.format() + +The usual pygor folder types are given in the technical reports +PTN003.ifn and TN003.ifn. +""" +import io, locale, re, sys + +import numpy as np + +from .binarywave import MAXDIMS as _MAXDIMS +from .packed import load_pack +from .record import ( + UnknownRecord, + FolderStartRecord, + FolderEndRecord, + HistoryRecord, + GetHistoryRecord, + RecreationRecord, + PackedFileRecord, + ProcedureRecord, + WaveRecord, + VariablesRecord, +) + +__all__ = [ + "load", + "Variables", + "History", + "Wave", + "Recreation", + "Procedure", + "GetHistory", + "PackedFile", + "Folder", +] + +ENCODING = locale.getpreferredencoding() or sys.getdefaultencoding() +PYKEYWORDS = set( + ( + "and", + "as", + "assert", + "break", + "class", + "continue", + "def", + "elif", + "else", + "except", + "exec", + "finally", + "for", + "global", + "if", + "import", + "in", + "is", + "lambda", + "or", + "pass", + "print", + "raise", + "return", + "try", + "with", + "yield", + ) +) +PYID = re.compile(r"^[^\d\W]\w*$", re.UNICODE) + + +def valid_identifier(s): + """Check if a name is a valid identifier""" + return PYID.match(s) and s not in PYKEYWORDS + + +class IgorObject(object): + """Parent class for all objects the parser can return""" + + pass + + +class Variables(IgorObject): + """ + Contains system numeric variables (e.g., K0) and user numeric and string variables. + """ + + def __init__(self, record): + self.sysvar = record.variables["variables"]["sysVars"] + self.uservar = record.variables["variables"]["userVars"] + self.userstr = record.variables["variables"]["userStrs"] + self.depvar = record.variables["variables"].get("dependentVars", {}) + self.depstr = record.variables["variables"].get("dependentStrs", {}) + + def format(self, indent=0): + return " " * indent + "" % ( + len(self.sysvar), + len(self.uservar) + len(self.userstr), + len(self.depvar) + len(self.depstr), + ) + + +class History(IgorObject): + """ + Contains the experiment's history as plain text. + """ + + def __init__(self, data): + self.data = data + + def format(self, indent=0): + return " " * indent + "" + + +class Wave(IgorObject): + """ + Contains the data for a wave + """ + + def __init__(self, record): + d = record.wave["wave"] + self.name = d["wave_header"]["bname"].decode(ENCODING) + self.data = d["wData"] + self.fs = d["wave_header"]["fsValid"] + self.fstop = d["wave_header"]["topFullScale"] + self.fsbottom = d["wave_header"]["botFullScale"] + version = record.wave["version"] + if version in [1, 2, 3]: + dims = [d["wave_header"]["npnts"]] + [0] * (_MAXDIMS - 1) + sfA = [d["wave_header"]["hsA"]] + [0] * (_MAXDIMS - 1) + sfB = [d["wave_header"]["hsB"]] + [0] * (_MAXDIMS - 1) + self.data_units = [d["wave_header"]["dataUnits"]] + self.axis_units = [d["wave_header"]["xUnits"]] + else: + dims = d["wave_header"]["nDim"] + sfA = d["wave_header"]["sfA"] + sfB = d["wave_header"]["sfB"] + # TODO find example with multiple data units + if version == 5: + self.data_units = [d["data_units"].decode(ENCODING)] + self.axis_units = [ + b"".join(d).decode(ENCODING) for d in d["wave_header"]["dimUnits"] + ] + else: + self.data_units = [d["data_units"].decode(ENCODING)] + self.axis_units = [d["dimension_units"].decode(ENCODING)] + + self.data_units.extend([""] * (_MAXDIMS - len(self.data_units))) + self.data_units = tuple(self.data_units) + self.axis_units.extend([""] * (_MAXDIMS - len(self.axis_units))) + self.axis_units = tuple(self.axis_units) + self.axis = [ + np.linspace(b, b + a * (c - 1), c) for a, b, c in zip(sfA, sfB, dims) + ] + self.formula = d.get("formula", "") + self.notes = d.get("note", "") + + def format(self, indent=0): + if isinstance(self.data, list): + type, size = "text", "%d" % len(self.data) + else: + type, size = "data", "x".join(str(d) for d in self.data.shape) + return " " * indent + "%s %s (%s)" % (self.name, type, size) + + def __array__(self): + return self.data + + __repr__ = __str__ = lambda s: "" % s.format() + + +class Recreation(IgorObject): + """ + Contains the experiment's recreation procedures as plain text. + """ + + def __init__(self, data): + self.data = data + + def format(self, indent=0): + return " " * indent + "" + + +class Procedure(IgorObject): + """ + Contains the experiment's main procedure window text as plain text. + """ + + def __init__(self, data): + self.data = data + + def format(self, indent=0): + return " " * indent + "" + + +class GetHistory(IgorObject): + """ + Not a real record but rather, a message to go back and read the history text. + + The reason for GetHistory is that IGOR runs Recreation when it loads the + datafile. This puts entries in the history that shouldn't be there. The + GetHistory entry simply says that the Recreation has run, and the History + can be restored from the previously saved value. + """ + + def __init__(self, data): + self.data = data + + def format(self, indent=0): + return " " * indent + "" + + +class PackedFile(IgorObject): + """ + Contains the data for a procedure file or notebook in packed form. + """ + + def __init__(self, data): + self.data = data + + def format(self, indent=0): + return " " * indent + "" + + +class Unknown(IgorObject): + """ + Record type not documented in PTN003/TN003. + """ + + def __init__(self, data, type): + self.data = data + self.type = type + + def format(self, indent=0): + return " " * indent + "" % self.type + + +class Folder(IgorObject): + """ + Hierarchical record container. + """ + + def __init__(self, path): + self.name = path[-1] + self.path = path + self.children = [] + + def __getitem__(self, key): + if isinstance(key, int): + return self.children[key] + else: + for r in self.children: + if isinstance(r, (Folder, Wave)) and r.name == key: + return r + raise KeyError("Folder %s does not exist" % key) + + def __str__(self): + return "" % "/".join(self.path) + + __repr__ = __str__ + + def append(self, record): + """ + Add a record to the folder. + """ + self.children.append(record) + try: + # Record may not have a name, the name may be invalid, or it + # may already be in use. The noname case will be covered by + # record.name raising an attribute error. The others we need + # to test for explicitly. + if valid_identifier(record.name) and not hasattr(self, record.name): + setattr(self, record.name, record) + except AttributeError: + pass + + def format(self, indent=0): + parent = " " * indent + self.name + children = [r.format(indent=indent + 2) for r in self.children] + return "\n".join([parent] + children) + + +def loads(s, **kwargs): + """Load an pygor file from string""" + stream = io.BytesIO(s) + return load(stream, **kwargs) + + +def load(filename, **kwargs): + """Load an pygor file""" + try: + packed_experiment = load_pack( + filename, initial_byte_order=kwargs.pop("initial_byte_order", "=") + ) + except ValueError as e: + if e.args[0].startswith("not enough data for the next record header"): + raise IOError("invalid record header; bad pxp file?") + elif e.args[0].startswith("not enough data for the next record"): + raise IOError("final record too long; bad pxp file?") + raise + return _convert(packed_experiment, **kwargs) + + +def _convert(packed_experiment, ignore_unknown=True): + records, filesystem = packed_experiment + stack = [Folder(path=["root"])] + for record in records: + if isinstance(record, UnknownRecord): + if ignore_unknown: + continue + else: + r = Unknown(record.data, type=record.header["recordType"]) + elif isinstance(record, GetHistoryRecord): + r = GetHistory(record.text) + elif isinstance(record, HistoryRecord): + r = History(record.text) + elif isinstance(record, PackedFileRecord): + r = PackedFile(record.text) + elif isinstance(record, ProcedureRecord): + r = Procedure(record.text) + elif isinstance(record, RecreationRecord): + r = Recreation(record.text) + elif isinstance(record, VariablesRecord): + r = Variables(record) + elif isinstance(record, WaveRecord): + r = Wave(record) + else: + r = None + + if isinstance(record, FolderStartRecord): + path = stack[-1].path + [record.null_terminated_text.decode(ENCODING)] + folder = Folder(path) + stack[-1].append(folder) + stack.append(folder) + elif isinstance(record, FolderEndRecord): + stack.pop() + elif r is None: + raise NotImplementedError(record) + else: + stack[-1].append(r) + if len(stack) != 1: + raise IOError("FolderStart records do not match FolderEnd records") + return stack[0] diff --git a/igor/packed.py b/src/pygor/packed.py similarity index 55% rename from igor/packed.py rename to src/pygor/packed.py index 2035410..785a868 100644 --- a/igor/packed.py +++ b/src/pygor/packed.py @@ -1,36 +1,34 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . "Read IGOR Packed Experiment files files into records." -from . import LOG as _LOG -from .struct import Structure as _Structure -from .struct import Field as _Field -from .util import byte_order as _byte_order -from .util import need_to_reorder_bytes as _need_to_reorder_bytes -from .util import _bytes -from .record import RECORD_TYPE as _RECORD_TYPE -from .record.base import UnknownRecord as _UnknownRecord -from .record.base import UnusedRecord as _UnusedRecord -from .record.folder import FolderStartRecord as _FolderStartRecord -from .record.folder import FolderEndRecord as _FolderEndRecord -from .record.variables import VariablesRecord as _VariablesRecord -from .record.wave import WaveRecord as _WaveRecord - +from .struct import Structure, Field +from .util import byte_order as get_byte_order +from .util import need_to_reorder_bytes +from .record import ( + RECORD_TYPE, + UnknownRecord, + UnusedRecord, + FolderStartRecord, + FolderEndRecord, + VariablesRecord, + WaveRecord, +) # From PTN003: # Igor writes other kinds of records in a packed experiment file, for @@ -40,32 +38,38 @@ # files, you must skip any record with a record type that is not # listed above. -PackedFileRecordHeader = _Structure( - name='PackedFileRecordHeader', +PackedFileRecordHeader = Structure( + name="PackedFileRecordHeader", fields=[ - _Field('H', 'recordType', help='Record type plus superceded flag.'), - _Field('h', 'version', help='Version information depends on the type of record.'), - _Field('l', 'numDataBytes', help='Number of data bytes in the record following this record header.'), - ]) - -#CR_STR = '\x15' (\r) + Field("H", "recordType", help="Record type plus superceded flag."), + Field( + "h", "version", help="Version information depends on the type of record." + ), + Field( + "l", + "numDataBytes", + help="Number of data bytes in the record following this record header.", + ), + ], +) + +# CR_STR = '\x15' (\r) PACKEDRECTYPE_MASK = 0x7FFF # Record type = (recordType & PACKEDREC_TYPE_MASK) SUPERCEDED_MASK = 0x8000 # Bit is set if the record is superceded by - # a later record in the packed file. +# a later record in the packed file. -def load(filename, strict=True, ignore_unknown=True, initial_byte_order='='): +def load_pack(filename, strict=True, ignore_unknown=True, initial_byte_order="="): """ Probably better to actually infer the initial_byte_order, as can be done from the header. For now though we will let the user deal with this. """ - _LOG.debug('loading a packed experiment file from {}'.format(filename)) records = [] - if hasattr(filename, 'read'): + if hasattr(filename, "read"): f = filename # filename is actually a stream object else: - f = open(filename, 'rb') + f = open(filename, "rb") byte_order = None try: while True: @@ -76,46 +80,40 @@ def load(filename, strict=True, ignore_unknown=True, initial_byte_order='='): break if len(b) < PackedFileRecordHeader.size: raise ValueError( - ('not enough data for the next record header ({} < {})' - ).format(len(b), PackedFileRecordHeader.size)) - _LOG.debug('reading a new packed experiment file record') + ("not enough data for the next record header ({} < {})").format( + len(b), PackedFileRecordHeader.size + ) + ) header = PackedFileRecordHeader.unpack_from(b) - if header['version'] and not byte_order: - need_to_reorder = _need_to_reorder_bytes(header['version']) - byte_order = initial_byte_order = _byte_order(need_to_reorder) - _LOG.debug( - 'get byte order from version: {} (reorder? {})'.format( - byte_order, need_to_reorder)) - if need_to_reorder: + if header["version"] and not byte_order: + should_reorder = need_to_reorder_bytes(header["version"]) + byte_order = initial_byte_order = get_byte_order(should_reorder) + if should_reorder: PackedFileRecordHeader.byte_order = byte_order PackedFileRecordHeader.setup() header = PackedFileRecordHeader.unpack_from(b) - _LOG.debug( - 'reordered version: {}'.format(header['version'])) - data = bytes(f.read(header['numDataBytes'])) - if len(data) < header['numDataBytes']: + data = bytes(f.read(header["numDataBytes"])) + if len(data) < header["numDataBytes"]: raise ValueError( - ('not enough data for the next record ({} < {})' - ).format(len(b), header['numDataBytes'])) - record_type = _RECORD_TYPE.get( - header['recordType'] & PACKEDRECTYPE_MASK, _UnknownRecord) - _LOG.debug('the new record has type {} ({}).'.format( - record_type, header['recordType'])) - if record_type in [_UnknownRecord, _UnusedRecord - ] and not ignore_unknown: - raise KeyError('unkown record type {}'.format( - header['recordType'])) + ("not enough data for the next record ({} < {})").format( + len(b), header["numDataBytes"] + ) + ) + record_type = RECORD_TYPE.get( + header["recordType"] & PACKEDRECTYPE_MASK, UnknownRecord + ) + if record_type in [UnknownRecord, UnusedRecord] and not ignore_unknown: + raise KeyError("unkown record type {}".format(header["recordType"])) records.append(record_type(header, data, byte_order=byte_order)) finally: - _LOG.debug('finished loading {} records from {}'.format( - len(records), filename)) - if not hasattr(filename, 'read'): + if not hasattr(filename, "read"): f.close() filesystem = _build_filesystem(records) return (records, filesystem) + def _build_filesystem(records): # From PTN003: """The name must be a valid Igor data folder name. See Object @@ -131,7 +129,7 @@ def _build_filesystem(records): """Like the Macintosh file system, Igor Pro's data folders use the colon character (:) to separate components of a path to an object. This is analogous to Unix which uses / and Windows which - uses \. (Reminder: Igor's data folders exist wholly in memory + uses \\. (Reminder: Igor's data folders exist wholly in memory while an experiment is open. It is not a disk file system!) A data folder named "root" always exists and contains all other @@ -151,20 +149,20 @@ def _build_filesystem(records): " ' : ; """ - filesystem = {'root': {}} - dir_stack = [('root', filesystem['root'])] + filesystem = {"root": {}} + dir_stack = [("root", filesystem["root"])] for record in records: cwd = dir_stack[-1][-1] - if isinstance(record, _FolderStartRecord): + if isinstance(record, FolderStartRecord): name = record.null_terminated_text cwd[name] = {} dir_stack.append((name, cwd[name])) - elif isinstance(record, _FolderEndRecord): + elif isinstance(record, FolderEndRecord): dir_stack.pop() - elif isinstance(record, (_VariablesRecord, _WaveRecord)): - if isinstance(record, _VariablesRecord): - sys_vars = record.variables['variables']['sysVars'].keys() - for filename,value in record.namespace.items(): + elif isinstance(record, (VariablesRecord, WaveRecord)): + if isinstance(record, VariablesRecord): + sys_vars = record.variables["variables"]["sysVars"].keys() + for filename, value in record.namespace.items(): if len(dir_stack) > 1 and filename in sys_vars: # From PTN003: """When reading a packed file, any system @@ -175,23 +173,27 @@ def _build_filesystem(records): _check_filename(dir_stack, filename) cwd[filename] = value else: # WaveRecord - filename = record.wave['wave']['wave_header']['bname'] + filename = record.wave["wave"]["wave_header"]["bname"] _check_filename(dir_stack, filename) cwd[filename] = record return filesystem + def _check_filename(dir_stack, filename): cwd = dir_stack[-1][-1] if filename in cwd: - raise ValueError('collision on name {} in {}'.format( - filename, ':'.join(d for d,cwd in dir_stack))) + raise ValueError( + "collision on name {} in {}".format( + filename, ":".join(d for d, cwd in dir_stack) + ) + ) + def walk(filesystem, callback, dirpath=None): - """Walk a packed experiment filesystem, operating on each key,value pair. - """ + """Walk a packed experiment filesystem, operating on each key,value pair.""" if dirpath is None: dirpath = [] - for key,value in sorted((_bytes(k),v) for k,v in filesystem.items()): + for key, value in sorted((bytes(k), v) for k, v in filesystem.items()): callback(dirpath, key, value) if isinstance(value, dict): - walk(filesystem=value, callback=callback, dirpath=dirpath+[key]) + walk(filesystem=value, callback=callback, dirpath=dirpath + [key]) diff --git a/igor/record/__init__.py b/src/pygor/record/__init__.py similarity index 82% rename from igor/record/__init__.py rename to src/pygor/record/__init__.py index eafebfb..9bf2d83 100644 --- a/igor/record/__init__.py +++ b/src/pygor/record/__init__.py @@ -1,19 +1,19 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . "Record parsers for IGOR's packed experiment files." @@ -40,4 +40,4 @@ 8: PackedFileRecord, 9: FolderStartRecord, 10: FolderEndRecord, - } +} diff --git a/igor/record/base.py b/src/pygor/record/base.py similarity index 55% rename from igor/record/base.py rename to src/pygor/record/base.py index 6b168cf..003996c 100644 --- a/igor/record/base.py +++ b/src/pygor/record/base.py @@ -1,22 +1,22 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . -class Record (object): +class Record(object): def __init__(self, header, data, byte_order=None): self.header = header self.data = data @@ -26,22 +26,22 @@ def __str__(self): return self.__repr__() def __repr__(self): - return '<{} {}>'.format(self.__class__.__name__, id(self)) + return "<{} {}>".format(self.__class__.__name__, id(self)) -class UnknownRecord (Record): +class UnknownRecord(Record): def __repr__(self): - return '<{}-{} {}>'.format( - self.__class__.__name__, self.header['recordType'], id(self)) + return "<{}-{} {}>".format( + self.__class__.__name__, self.header["recordType"], id(self) + ) -class UnusedRecord (Record): +class UnusedRecord(Record): pass -class TextRecord (Record): +class TextRecord(Record): def __init__(self, *args, **kwargs): super(TextRecord, self).__init__(*args, **kwargs) - self.text = bytes(self.data).replace( - b'\r\n', b'\n').replace(b'\r', b'\n') - self.null_terminated_text = self.text.split(b'\x00', 1)[0] + self.text = bytes(self.data).replace(b"\r\n", b"\n").replace(b"\r", b"\n") + self.null_terminated_text = self.text.split(b"\x00", 1)[0] diff --git a/igor/record/folder.py b/src/pygor/record/folder.py similarity index 62% rename from igor/record/folder.py rename to src/pygor/record/folder.py index caaeb54..c8a71bb 100644 --- a/igor/record/folder.py +++ b/src/pygor/record/folder.py @@ -1,26 +1,26 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . from .base import TextRecord -class FolderStartRecord (TextRecord): +class FolderStartRecord(TextRecord): pass -class FolderEndRecord (TextRecord): +class FolderEndRecord(TextRecord): pass diff --git a/igor/record/history.py b/src/pygor/record/history.py similarity index 60% rename from igor/record/history.py rename to src/pygor/record/history.py index e5d2199..05bc309 100644 --- a/igor/record/history.py +++ b/src/pygor/record/history.py @@ -1,30 +1,30 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . from .base import TextRecord -class HistoryRecord (TextRecord): +class HistoryRecord(TextRecord): pass -class RecreationRecord (TextRecord): +class RecreationRecord(TextRecord): pass -class GetHistoryRecord (TextRecord): +class GetHistoryRecord(TextRecord): pass diff --git a/igor/record/packedfile.py b/src/pygor/record/packedfile.py similarity index 64% rename from igor/record/packedfile.py rename to src/pygor/record/packedfile.py index b457f20..c4ddaf5 100644 --- a/igor/record/packedfile.py +++ b/src/pygor/record/packedfile.py @@ -1,22 +1,22 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . from .base import Record -class PackedFileRecord (Record): +class PackedFileRecord(Record): pass diff --git a/igor/record/procedure.py b/src/pygor/record/procedure.py similarity index 64% rename from igor/record/procedure.py rename to src/pygor/record/procedure.py index de00e6e..b3a5629 100644 --- a/igor/record/procedure.py +++ b/src/pygor/record/procedure.py @@ -1,22 +1,22 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . from .base import TextRecord -class ProcedureRecord (TextRecord): +class ProcedureRecord(TextRecord): pass diff --git a/src/pygor/record/variables.py b/src/pygor/record/variables.py new file mode 100644 index 0000000..31420f9 --- /dev/null +++ b/src/pygor/record/variables.py @@ -0,0 +1,371 @@ +# Copyright (C) 2012 W. Trevor King +# +# This file is part of pygor. +# +# pygor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with pygor. If not, see . + +import io + +from ..binarywave import TYPE_TABLE as TYPE_TABLE +from ..binarywave import NullStaticStringField, DynamicStringField +from ..struct import Structure, DynamicStructure, Field, DynamicField +from ..util import byte_order, need_to_reorder_bytes +from .base import Record + + +class ListedStaticStringField(NullStaticStringField): + """Handle string conversions for multi-count dynamic parents. + + If a field belongs to a multi-count dynamic parent, the parent is + called multiple times to parse each count, and the field's + post-unpack hook gets called after the field is unpacked during + each iteration. This requires alternative logic for getting and + setting the string data. The actual string formatting code is not + affected. + """ + + def post_unpack(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + d = self._normalize_string(parent_data[-1][self.name]) + parent_data[-1][self.name] = d + + +class ListedStaticStringField(NullStaticStringField): + """Handle string conversions for multi-count dynamic parents. + + If a field belongs to a multi-count dynamic parent, the parent is + called multiple times to parse each count, and the field's + post-unpack hook gets called after the field is unpacked during + each iteration. This requires alternative logic for getting and + setting the string data. The actual string formatting code is not + affected. + """ + + def post_unpack(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + d = self._normalize_string(parent_data[-1][self.name]) + parent_data[-1][self.name] = d + + +class ListedDynamicStrDataField(DynamicStringField, ListedStaticStringField): + _size_field = "strLen" + _null_terminated = False + + def _get_size_data(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + return parent_data[-1][self._size_field] + + +class DynamicVarDataField(DynamicField): + def __init__(self, *args, **kwargs): + if "array" not in kwargs: + kwargs["array"] = True + super(DynamicVarDataField, self).__init__(*args, **kwargs) + + def pre_pack(self, parents, data): + raise NotImplementedError() + + def post_unpack(self, parents, data): + var_structure = parents[-1] + var_data = self._get_structure_data(parents, data, var_structure) + data = var_data[self.name] + d = {} + for i, value in enumerate(data): + key, value = self._normalize_item(i, value) + d[key] = value + var_data[self.name] = d + + def _normalize_item(self, index, value): + raise NotImplementedError() + + +class DynamicSysVarField(DynamicVarDataField): + def _normalize_item(self, index, value): + name = "K{}".format(index) + return (name, value) + + +class DynamicUserVarField(DynamicVarDataField): + def _normalize_item(self, index, value): + name = value["name"] + value = value["num"] + return (name, value) + + +class DynamicUserStrField(DynamicVarDataField): + def _normalize_item(self, index, value): + name = value["name"] + value = value["data"] + return (name, value) + + +class DynamicVarNumField(DynamicField): + def post_unpack(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + d = self._normalize_numeric_variable(parent_data[-1][self.name]) + parent_data[-1][self.name] = d + + def _normalize_numeric_variable(self, num_var): + t = TYPE_TABLE[num_var["numType"]] + if num_var["numType"] % 2: # complex number + return t(complex(num_var["realPart"], num_var["imagPart"])) + else: + return t(num_var["realPart"]) + + +class DynamicFormulaField(DynamicStringField): + _size_field = "formulaLen" + _null_terminated = True + + +# From Variables.h +VarHeader1 = Structure( # `version` field pulled out into VariablesRecord + name="VarHeader1", + fields=[ + Field("h", "numSysVars", help="Number of system variables (K0, K1, ...)."), + Field( + "h", "numUserVars", help="Number of user numeric variables -- may be zero." + ), + Field( + "h", "numUserStrs", help="Number of user string variables -- may be zero." + ), + ], +) + +# From Variables.h +VarHeader2 = Structure( # `version` field pulled out into VariablesRecord + name="VarHeader2", + fields=[ + Field("h", "numSysVars", help="Number of system variables (K0, K1, ...)."), + Field( + "h", "numUserVars", help="Number of user numeric variables -- may be zero." + ), + Field( + "h", "numUserStrs", help="Number of user string variables -- may be zero." + ), + Field( + "h", + "numDependentVars", + help="Number of dependent numeric variables -- may be zero.", + ), + Field( + "h", + "numDependentStrs", + help="Number of dependent string variables -- may be zero.", + ), + ], +) + +# From Variables.h +UserStrVarRec1 = DynamicStructure( + name="UserStrVarRec1", + fields=[ + ListedStaticStringField( + "c", "name", help="Name of the string variable.", count=32 + ), + Field("h", "strLen", help="The real size of the following array."), + ListedDynamicStrDataField("c", "data"), + ], +) + +# From Variables.h +UserStrVarRec2 = DynamicStructure( + name="UserStrVarRec2", + fields=[ + ListedStaticStringField( + "c", "name", help="Name of the string variable.", count=32 + ), + Field("l", "strLen", help="The real size of the following array."), + Field("c", "data"), + ], +) + +# From Variables.h +VarNumRec = Structure( + name="VarNumRec", + fields=[ + Field("h", "numType", help="Type from binarywave.TYPE_TABLE"), + Field("d", "realPart", help="The real part of the number."), + Field("d", "imagPart", help="The imag part if the number is complex."), + Field("l", "reserved", help="Reserved - set to zero."), + ], +) + +# From Variables.h +UserNumVarRec = DynamicStructure( + name="UserNumVarRec", + fields=[ + ListedStaticStringField( + "c", "name", help="Name of the string variable.", count=32 + ), + Field("h", "type", help="0 = string, 1 = numeric."), + DynamicVarNumField( + VarNumRec, + "num", + help="Type and value of the variable if it is numeric. Not used for string.", + ), + ], +) + +# From Variables.h +UserDependentVarRec = DynamicStructure( + name="UserDependentVarRec", + fields=[ + ListedStaticStringField( + "c", "name", help="Name of the string variable.", count=32 + ), + Field("h", "type", help="0 = string, 1 = numeric."), + Field( + VarNumRec, + "num", + help="Type and value of the variable if it is numeric. Not used for string.", + ), + Field("h", "formulaLen", help="The length of the dependency formula."), + DynamicFormulaField( + "c", + "formula", + help="Start of the dependency formula. A C string including null terminator.", + ), + ], +) + + +class DynamicVarHeaderField(DynamicField): + def pre_pack(self, parents, data): + raise NotImplementedError() + + def post_unpack(self, parents, data): + var_structure = parents[-1] + var_data = self._get_structure_data(parents, data, var_structure) + var_header_structure = self.format + data = var_data["var_header"] + sys_vars_field = var_structure.get_field("sysVars") + sys_vars_field.count = data["numSysVars"] + sys_vars_field.setup() + user_vars_field = var_structure.get_field("userVars") + user_vars_field.count = data["numUserVars"] + user_vars_field.setup() + user_strs_field = var_structure.get_field("userStrs") + user_strs_field.count = data["numUserStrs"] + user_strs_field.setup() + if "numDependentVars" in data: + dependent_vars_field = var_structure.get_field("dependentVars") + dependent_vars_field.count = data["numDependentVars"] + dependent_vars_field.setup() + dependent_strs_field = var_structure.get_field("dependentStrs") + dependent_strs_field.count = data["numDependentStrs"] + dependent_strs_field.setup() + var_structure.setup() + + +Variables1 = DynamicStructure( + name="Variables1", + fields=[ + DynamicVarHeaderField(VarHeader1, "var_header", help="Variables header"), + DynamicSysVarField("f", "sysVars", help="System variables", count=0), + DynamicUserVarField( + UserNumVarRec, "userVars", help="User numeric variables", count=0 + ), + DynamicUserStrField( + UserStrVarRec1, "userStrs", help="User string variables", count=0 + ), + ], +) + + +Variables2 = DynamicStructure( + name="Variables2", + fields=[ + DynamicVarHeaderField(VarHeader2, "var_header", help="Variables header"), + DynamicSysVarField("f", "sysVars", help="System variables", count=0), + DynamicUserVarField( + UserNumVarRec, "userVars", help="User numeric variables", count=0 + ), + DynamicUserStrField( + UserStrVarRec2, "userStrs", help="User string variables", count=0 + ), + Field( + UserDependentVarRec, + "dependentVars", + help="Dependent numeric variables.", + count=0, + array=True, + ), + Field( + UserDependentVarRec, + "dependentStrs", + help="Dependent string variables.", + count=0, + array=True, + ), + ], +) + + +class DynamicVersionField(DynamicField): + def pre_pack(self, parents, byte_order): + raise NotImplementedError() + + def post_unpack(self, parents, data): + variables_structure = parents[-1] + variables_data = self._get_structure_data(parents, data, variables_structure) + version = variables_data["version"] + if variables_structure.byte_order in "@=": + should_reorder_bytes = need_to_reorder_bytes(version) + variables_structure.byte_order = byte_order(should_reorder_bytes) + else: + should_reorder_bytes = False + + old_format = variables_structure.fields[-1].format + if version == 1: + variables_structure.fields[-1].format = Variables1 + elif version == 2: + variables_structure.fields[-1].format = Variables2 + elif not should_reorder_bytes: + raise ValueError("invalid variables record version: {}".format(version)) + + if variables_structure.fields[-1].format != old_format: + variables_structure.setup() + elif should_reorder_bytes: + variables_structure.setup() + + # we might need to unpack again with the new byte order + return should_reorder_bytes + + +VariablesRecordStructure = DynamicStructure( + name="VariablesRecord", + fields=[ + DynamicVersionField("h", "version", help="Version number for this header."), + Field(Variables1, "variables", help="The rest of the variables data."), + ], +) + + +class VariablesRecord(Record): + def __init__(self, *args, **kwargs): + super(VariablesRecord, self).__init__(*args, **kwargs) + # self.header['version'] # record version always 0? + VariablesRecordStructure.byte_order = "=" + VariablesRecordStructure.setup() + stream = io.BytesIO(bytes(self.data)) + self.variables = VariablesRecordStructure.unpack_stream(stream) + self.namespace = {} + for key, value in self.variables["variables"].items(): + if key not in ["var_header"]: + self.namespace.update(value) diff --git a/igor/record/wave.py b/src/pygor/record/wave.py similarity index 61% rename from igor/record/wave.py rename to src/pygor/record/wave.py index 49ed20e..ceb2914 100644 --- a/igor/record/wave.py +++ b/src/pygor/record/wave.py @@ -1,30 +1,30 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . -from io import BytesIO as _BytesIO +from io import BytesIO -from ..binarywave import load as _loadibw +from ..binarywave import load_ibw from . import Record -class WaveRecord (Record): +class WaveRecord(Record): def __init__(self, *args, **kwargs): super(WaveRecord, self).__init__(*args, **kwargs) - self.wave = _loadibw(_BytesIO(bytes(self.data))) + self.wave = load_ibw(BytesIO(bytes(self.data))) def __str__(self): return str(self.wave) diff --git a/igor/struct.py b/src/pygor/struct.py similarity index 79% rename from igor/struct.py rename to src/pygor/struct.py index a50ede5..6f16e41 100644 --- a/igor/struct.py +++ b/src/pygor/struct.py @@ -1,19 +1,19 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . """Structure and Field classes for declaring structures @@ -23,18 +23,12 @@ with each field in a hierarchy of Python dictionaries. """ -from __future__ import absolute_import -import io as _io -import logging as _logging -import pprint as _pprint -import struct as _struct +import io, struct -import numpy as _numpy +import numpy as np -from . import LOG as _LOG - -class Field (object): +class Field(object): """Represent a Structure field. The format argument can be a format character from the ``struct`` @@ -148,8 +142,8 @@ class Field (object): -------- Structure """ - def __init__(self, format, name, default=None, help=None, count=1, - array=False): + + def __init__(self, format, name, default=None, help=None, count=1, array=False): self.format = format self.name = name self.default = default @@ -164,17 +158,17 @@ def setup(self): Use this method to recalculate dynamic properities after changing the basic properties set during initialization. """ - _LOG.debug('setup {}'.format(self)) - self.item_count = _numpy.prod(self.count) # number of item repeats + self.item_count = np.prod(self.count) # number of item repeats if not self.array and self.item_count != 1: raise ValueError( - '{} must be an array field to have a count of {}'.format( - self, self.count)) + "{} must be an array field to have a count of {}".format( + self, self.count + ) + ) if isinstance(self.format, Structure): - self.structure_count = sum( - f.arg_count for f in self.format.fields) + self.structure_count = sum(f.arg_count for f in self.format.fields) self.arg_count = self.item_count * self.structure_count - elif self.format == 'x': + elif self.format == "x": self.arg_count = 0 # no data in padding bytes else: self.arg_count = self.item_count # struct.Struct format args @@ -183,8 +177,7 @@ def __str__(self): return self.__repr__() def __repr__(self): - return '<{} {} {}>'.format( - self.__class__.__name__, self.name, id(self)) + return "<{} {} {}>".format(self.__class__.__name__, self.name, id(self)) def indexes(self): """Iterate through indexes to a possibly multi-dimensional array""" @@ -197,7 +190,7 @@ def indexes(self): else: for i in range(self.item_count): index = [] - for j,c in enumerate(reversed(self.count)): + for j, c in enumerate(reversed(self.count)): index.insert(0, i % c) i //= c yield index @@ -211,18 +204,17 @@ def pack_data(self, data=None): if self.array: if data is None: data = [] - if hasattr(data, 'flat'): # take advantage of numpy's ndarray.flat + if hasattr(data, "flat"): # take advantage of numpy's ndarray.flat items = 0 for item in data.flat: items += 1 for arg in self.pack_item(item): yield arg - if items < self.item_count: - if f.default is None: - raise ValueError( - 'no default for {}.{}'.format(self, f)) - for i in range(self.item_count - items): - yield f.default + # if items < self.item_count: + # if f.default is None: + # raise ValueError("no default for {}.{}".format(self, f)) + # for i in range(self.item_count - items): + # yield f.default else: for index in self.indexes(): try: @@ -241,32 +233,30 @@ def pack_data(self, data=None): yield arg def pack_item(self, item=None): - """Linearize a single count of the field's data to a flat iterable - """ + """Linearize a single count of the field's data to a flat iterable""" if isinstance(self.format, Structure): for i in self.format._pack_item(item): yield i elif item is None: if self.default is None: - raise ValueError('no default for {}'.format(self)) + raise ValueError("no default for {}".format(self)) yield self.default else: yield item def unpack_data(self, data): """Inverse of .pack_data""" - _LOG.debug('unpack {} for {} {}'.format(data, self, self.format)) iterator = iter(data) try: items = [next(iterator) for i in range(self.arg_count)] except StopIteration: - raise ValueError('not enough data to unpack {}'.format(self)) + raise ValueError("not enough data to unpack {}".format(self)) try: next(iterator) except StopIteration: pass else: - raise ValueError('too much data to unpack {}'.format(self)) + raise ValueError("too much data to unpack {}".format(self)) if isinstance(self.format, Structure): # break into per-structure clumps s = self.structure_count @@ -287,11 +277,9 @@ def unpack_data(self, data): except TypeError: pass else: - raise NotImplementedError('reshape Structure field') + raise NotImplementedError("reshape Structure field") else: - unpacked = _numpy.array(unpacked) - _LOG.debug('reshape {} data from {} to {}'.format( - self, unpacked.shape, count)) + unpacked = np.array(unpacked) unpacked = unpacked.reshape(count) return unpacked @@ -304,14 +292,14 @@ def unpack_item(self, item): return item[0] -class DynamicField (Field): +class DynamicField(Field): """Represent a DynamicStructure field with a dynamic definition. Adds the methods ``.pre_pack``, ``pre_unpack``, and ``post_unpack``, all of which are called when a ``DynamicField`` is used by a ``DynamicStructure``. Each method takes the arguments ``(parents, data)``, where ``parents`` is a list of - ``DynamicStructure``\s that own the field and ``data`` is a dict + ``DynamicStructure``s that own the field and ``data`` is a dict hierarchy of the structure data. See the ``DynamicStructure`` docstring for the exact timing of the @@ -321,6 +309,7 @@ class DynamicField (Field): -------- Field, DynamicStructure """ + def pre_pack(self, parents, data): "Prepare to pack." pass @@ -334,8 +323,7 @@ def post_unpack(self, parents, data): pass def _get_structure_data(self, parents, data, structure): - """Extract the data belonging to a particular ancestor structure. - """ + """Extract the data belonging to a particular ancestor structure.""" d = data s = parents[0] if s == structure: @@ -352,7 +340,7 @@ def _get_structure_data(self, parents, data, structure): return d -class Structure (_struct.Struct): +class Structure(struct.Struct): r"""Represent a C structure. A convenient wrapper around struct.Struct that uses Fields and @@ -479,9 +467,10 @@ class Structure (_struct.Struct): >>> b2 == b True """ - _byte_order_symbols = '@=<>!' - def __init__(self, name, fields, byte_order='@'): + _byte_order_symbols = "@=<>!" + + def __init__(self, name, fields, byte_order="@"): # '=' for native byte order, standard size and alignment # See http://docs.python.org/library/struct for details self.name = name @@ -493,8 +482,7 @@ def __str__(self): return self.name def __repr__(self): - return '<{} {} {}>'.format( - self.__class__.__name__, self.name, id(self)) + return "<{} {} {}>".format(self.__class__.__name__, self.name, id(self)) def setup(self): """Setup any dynamic properties of a structure. @@ -502,44 +490,38 @@ def setup(self): Use this method to recalculate dynamic properities after changing the basic properties set during initialization. """ - _LOG.debug('setup {!r}'.format(self)) self.set_byte_order(self.byte_order) self.get_format() def set_byte_order(self, byte_order): - """Allow changing the format byte_order on the fly. - """ - _LOG.debug('set byte order for {!r} to {}'.format(self, byte_order)) + """Allow changing the format byte_order on the fly.""" self.byte_order = byte_order for field in self.fields: if isinstance(field.format, Structure): field.format.set_byte_order(byte_order) def get_format(self): - format = self.byte_order + ''.join(self.sub_format()) + format = self.byte_order + "".join(self.sub_format()) # P format only allowed for native byte ordering # Convert P to I for ILP32 compatibility when running on a LP64. - format = format.replace('P', 'I') + format = format.replace("P", "I") try: super(Structure, self).__init__(format=format) - except _struct.error as e: + except struct.error as e: raise ValueError((e, format)) return format def sub_format(self): - _LOG.debug('calculate sub-format for {!r}'.format(self)) for field in self.fields: if isinstance(field.format, Structure): - field_format = list( - field.format.sub_format()) * field.item_count + field_format = list(field.format.sub_format()) * field.item_count else: - field_format = [field.format]*field.item_count + field_format = [field.format] * field.item_count for fmt in field_format: yield fmt def _pack_item(self, item=None): - """Linearize a single count of the structure's data to a flat iterable - """ + """Linearize a single count of the structure's data to a flat iterable""" if item is None: item = {} for f in self.fields: @@ -560,15 +542,14 @@ def _unpack_item(self, args): try: items = [next(iterator) for i in range(f.arg_count)] except StopIteration: - raise ValueError('not enough data to unpack {}.{}'.format( - self, f)) + raise ValueError("not enough data to unpack {}.{}".format(self, f)) data[f.name] = f.unpack_data(items) try: next(iterator) except StopIteration: pass else: - raise ValueError('too much data to unpack {}'.format(self)) + raise ValueError("too much data to unpack {}".format(self)) return data def pack(self, data): @@ -580,37 +561,30 @@ def pack(self, data): def pack_into(self, buffer, offset=0, data={}): args = list(self._pack_item(data)) - return super(Structure, self).pack_into( - buffer, offset, *args) + return super(Structure, self).pack_into(buffer, offset, *args) def unpack(self, *args, **kwargs): args = super(Structure, self).unpack(*args, **kwargs) return self._unpack_item(args) def unpack_from(self, buffer, offset=0, *args, **kwargs): - _LOG.debug( - 'unpack {!r} for {!r} ({}, offset={}) with {} ({})'.format( - buffer, self, len(buffer), offset, self.format, self.size)) - args = super(Structure, self).unpack_from( - buffer, offset, *args, **kwargs) + args = super(Structure, self).unpack_from(buffer, offset, *args, **kwargs) return self._unpack_item(args) def get_field(self, name): return [f for f in self.fields if f.name == name][0] -class DebuggingStream (object): +class DebuggingStream(object): def __init__(self, stream): self.stream = stream def read(self, size): data = self.stream.read(size) - _LOG.debug('read {} from {}: ({}) {!r}'.format( - size, self.stream, len(data), data)) return data -class DynamicStructure (Structure): +class DynamicStructure(Structure): r"""Represent a C structure field with a dynamic definition. Any dynamic fields have their ``.pre_pack`` called before any @@ -698,7 +672,8 @@ class DynamicStructure (Structure): for ``Structure``, because we must make multiple calls to ``struct.Struct.unpack`` to unpack the data. """ - #def __init__(self, *args, **kwargs): + + # def __init__(self, *args, **kwargs): # pass #self.parent = .. def _pre_pack(self, parents=None, data=None): @@ -707,11 +682,9 @@ def _pre_pack(self, parents=None, data=None): else: parents = parents + [self] for f in self.fields: - if hasattr(f, 'pre_pack'): - _LOG.debug('pre-pack {}'.format(f)) + if hasattr(f, "pre_pack"): f.pre_pack(parents=parents, data=data) if isinstance(f.format, DynamicStructure): - _LOG.debug('pre-pack {!r}'.format(f.format)) f._pre_pack(parents=parents, data=data) def pack(self, data): @@ -723,29 +696,22 @@ def pack_into(self, buffer, offset=0, data={}): self._pre_pack(data=data) self.setup() return super(DynamicStructure, self).pack_into( - buffer=buffer, offset=offset, data=data) + buffer=buffer, offset=offset, data=data + ) def unpack_stream(self, stream, parents=None, data=None, d=None): # `d` is the working data directory if data is None: parents = [self] data = d = {} - if _LOG.level <= _logging.DEBUG: - stream = DebuggingStream(stream) else: parents = parents + [self] for f in self.fields: - _LOG.debug('parsing {!r}.{} (count={}, item_count={})'.format( - self, f, f.count, f.item_count)) - if _LOG.level <= _logging.DEBUG: - _LOG.debug('data:\n{}'.format(_pprint.pformat(data))) - if hasattr(f, 'pre_unpack'): - _LOG.debug('pre-unpack {}'.format(f)) + if hasattr(f, "pre_unpack"): f.pre_unpack(parents=parents, data=data) - if hasattr(f, 'unpack'): # override default unpacking - _LOG.debug('override unpack for {}'.format(f)) + if hasattr(f, "unpack"): # override default unpacking d[f.name] = f.unpack(stream) continue @@ -761,23 +727,24 @@ def unpack_stream(self, stream, parents=None, data=None, d=None): x = {} d[f.name].append(x) f.format.unpack_stream( - stream, parents=parents, data=data, d=x) + stream, parents=parents, data=data, d=x + ) else: assert f.item_count == 1, (f, f.count) d[f.name] = {} f.format.unpack_stream( - stream, parents=parents, data=data, d=d[f.name]) - if hasattr(f, 'post_unpack'): - _LOG.debug('post-unpack {}'.format(f)) + stream, parents=parents, data=data, d=d[f.name] + ) + if hasattr(f, "post_unpack"): repeat = f.post_unpack(parents=parents, data=data) if repeat: raise NotImplementedError( - 'cannot repeat unpack for dynamic structures') + "cannot repeat unpack for dynamic structures" + ) continue if isinstance(f.format, Structure): - _LOG.debug('parsing {} bytes for {}'.format( - f.format.size, f.format.format)) bs = [stream.read(f.format.size) for i in range(f.item_count)] + def unpack(): f.format.set_byte_order(self.byte_order) f.setup() @@ -787,50 +754,44 @@ def unpack(): assert len(x) == 1, (f, f.count, x) x = x[0] return x + else: - field_format = self.byte_order + f.format*f.item_count - field_format = field_format.replace('P', 'I') + field_format = self.byte_order + f.format * f.item_count + field_format = field_format.replace("P", "I") try: - size = _struct.calcsize(field_format) - except _struct.error as e: - _LOG.error(e) - _LOG.error('{}.{}: {}'.format(self, f, field_format)) + size = struct.calcsize(field_format) + except struct.error as e: raise - _LOG.debug('parsing {} bytes for preliminary {}'.format( - size, field_format)) raw = stream.read(size) if len(raw) < size: raise ValueError( - 'not enough data to unpack {}.{} ({} < {})'.format( - self, f, len(raw), size)) + "not enough data to unpack {}.{} ({} < {})".format( + self, f, len(raw), size + ) + ) + def unpack(): - field_format = self.byte_order + f.format*f.item_count - field_format = field_format.replace('P', 'I') - _LOG.debug('parse previous bytes using {}'.format( - field_format)) - struct = _struct.Struct(field_format) - items = struct.unpack(raw) + field_format = self.byte_order + f.format * f.item_count + field_format = field_format.replace("P", "I") + struct_instance = struct.Struct(field_format) + items = struct_instance.unpack(raw) return f.unpack_data(items) # unpacking loop repeat = True while repeat: d[f.name] = unpack() - if hasattr(f, 'post_unpack'): - _LOG.debug('post-unpack {}'.format(f)) + if hasattr(f, "post_unpack"): repeat = f.post_unpack(parents=parents, data=data) else: repeat = False - if repeat: - _LOG.debug('repeat unpack for {}'.format(f)) return data def unpack(self, string): - stream = _io.BytesIO(string) + stream = io.BytesIO(string) return self.unpack_stream(stream) def unpack_from(self, buffer, offset=0, *args, **kwargs): - args = super(Structure, self).unpack_from( - buffer, offset, *args, **kwargs) + args = super(Structure, self).unpack_from(buffer, offset, *args, **kwargs) return self._unpack_item(args) diff --git a/igor/util.py b/src/pygor/util.py similarity index 59% rename from igor/util.py rename to src/pygor/util.py index ecc783a..c3fad47 100644 --- a/igor/util.py +++ b/src/pygor/util.py @@ -1,39 +1,27 @@ # Copyright (C) 2012 W. Trevor King # -# This file is part of igor. +# This file is part of pygor. # -# igor is free software: you can redistribute it and/or modify it under the +# pygor is free software: you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # -# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# pygor is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License -# along with igor. If not, see . +# along with pygor. If not, see . "Utility functions for handling buffers" -import sys as _sys +import sys -import numpy as _numpy +import numpy as np -def _ord(byte): - r"""Convert a byte to an integer. - - >>> buffer = b'\x00\x01\x02' - >>> [_ord(b) for b in buffer] - [0, 1, 2] - """ - if _sys.version_info >= (3,): - return byte - else: - return ord(byte) - def hex_bytes(buffer, spaces=None): r"""Pretty-printing for binary buffers. @@ -48,14 +36,15 @@ def hex_bytes(buffer, spaces=None): >>> hex_bytes(b'\x00\x01\x02\x03\x04\x05\x06', spaces=3) '000102 030405 06' """ - hex_bytes = ['{:02x}'.format(_ord(x)) for x in buffer] + hex_bytes = ["{:02x}".format(x) for x in buffer] if spaces is None: - return ''.join(hex_bytes) - elif spaces is 1: - return ' '.join(hex_bytes) - for i in range(len(hex_bytes)//spaces): - hex_bytes.insert((spaces+1)*(i+1)-1, ' ') - return ''.join(hex_bytes) + return "".join(hex_bytes) + elif spaces == 1: + return " ".join(hex_bytes) + for i in range(len(hex_bytes) // spaces): + hex_bytes.insert((spaces + 1) * (i + 1) - 1, " ") + return "".join(hex_bytes) + def assert_null(buffer, strict=True): r"""Ensure an input buffer is entirely zero. @@ -73,22 +62,25 @@ def assert_null(buffer, strict=True): warning: post-data padding not zero: 00 01 02 03 >>> sys.stderr = stderr """ - if buffer and _ord(max(buffer)) != 0: + if buffer and max(buffer) != 0: hex_string = hex_bytes(buffer, spaces=1) if strict: raise ValueError(hex_string) else: - _sys.stderr.write( - 'warning: post-data padding not zero: {}\n'.format(hex_string)) + sys.stderr.write( + "warning: post-data padding not zero: {}\n".format(hex_string) + ) + # From ReadWave.c def byte_order(needToReorderBytes): - little_endian = _sys.byteorder == 'little' + little_endian = sys.byteorder == "little" if needToReorderBytes: little_endian = not little_endian if little_endian: - return '<' # little-endian - return '>' # big-endian + return "<" # little-endian + return ">" # big-endian + # From ReadWave.c def need_to_reorder_bytes(version): @@ -98,31 +90,17 @@ def need_to_reorder_bytes(version): # reordered. return version & 0xFF == 0 + # From ReadWave.c def checksum(buffer, byte_order, oldcksum, numbytes): - x = _numpy.ndarray( - (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte - dtype=_numpy.dtype(byte_order+'h'), - buffer=buffer) + x = np.ndarray( + (numbytes / 2,), # 2 bytes to a short -- ignore trailing odd byte + dtype=np.dtype(byte_order + "h"), + buffer=buffer, + ) oldcksum += x.sum() if oldcksum > 2**31: # fake the C implementation's int rollover oldcksum %= 2**32 if oldcksum > 2**31: oldcksum -= 2**31 - return oldcksum & 0xffff - -def _bytes(obj, encoding='utf-8'): - """Convert bytes or strings into bytes - - >>> _bytes(b'123') - '123' - >>> _bytes('123') - '123' - """ - if _sys.version_info >= (3,): - if isinstance(obj, bytes): - return obj - else: - return bytes(obj, encoding) - else: - return bytes(obj) + return oldcksum & 0xFFFF diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test-igorpy.py b/test/test-igorpy.py deleted file mode 100644 index c419311..0000000 --- a/test/test-igorpy.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright (C) 2012 W. Trevor King -# -# This file is part of %(project)s. -# -# %(project)s is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# %(project)s is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with %(project)s. If not, see . - -r"""Test the igor.igorpy compatibility layer by loading sample files. - ->>> from pprint import pprint ->>> import igor.igorpy as igor ->>> igor.ENCODING = 'UTF-8' - -Load a packed experiment: - ->>> path = data_path('polar-graphs-demo.pxp') ->>> d = igor.load(path) ->>> print(d) - ->>> dir(d) # doctest: +ELLIPSIS -['Packages', 'W_plrX5', 'W_plrX6', ..., 'radiusData', 'radiusQ1'] - - -Navigation: - ->>> print(d.Packages) - ->>> print(d[0]) # doctest: +ELLIPSIS - - - -Variables: - ->>> v = d[0] ->>> dir(v) # doctest: +ELLIPSIS -['__class__', ..., 'depstr', 'depvar', 'format', 'sysvar', 'userstr', 'uservar'] ->>> v.depstr -{} ->>> v.depvar -{} ->>> v.format() -'' ->>> pprint(v.sysvar) # doctest: +REPORT_UDIFF -{'K0': 0.0, - 'K1': 0.0, - 'K10': 0.0, - 'K11': 0.0, - 'K12': 0.0, - 'K13': 0.0, - 'K14': 0.0, - 'K15': 0.0, - 'K16': 0.0, - 'K17': 0.0, - 'K18': 0.0, - 'K19': 0.0, - 'K2': 0.0, - 'K20': 128.0, - 'K3': 0.0, - 'K4': 0.0, - 'K5': 0.0, - 'K6': 0.0, - 'K7': 0.0, - 'K8': 0.0, - 'K9': 0.0} ->>> v.userstr -{} ->>> v.uservar -{} - - -Waves: - ->>> d.W_plrX5 - ->>> dir(d.W_plrX5) # doctest: +ELLIPSIS -['__array__', ..., 'axis', 'axis_units', 'data', ..., 'name', 'notes'] ->>> d.W_plrX5.axis # doctest: +ELLIPSIS -[array([ 0.04908739, 0.04870087, 0.04831436, 0.04792784, 0.04754133, - 0.04715481, 0.0467683 , 0.04638178, 0.04599527, 0.04560875, - ... - 0.00077303, 0.00038651, 0. ]), array([], dtype=float64), array([], dtype=float64), array([], dtype=float64)] ->>> d.W_plrX5.data_units -(u'', '', '', '') ->>> d.W_plrX5.axis_units -(u'', '', '', '') ->>> d.W_plrX5.data # doctest: +ELLIPSIS -array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, - 1.44305170e-01, 2.23293692e-01, 3.04783821e-01, - ... - -2.72719120e-03, 5.24539061e-08], dtype=float32) - - -Dump the whole thing: - ->>> print(d.format()) -root - - - radiusData data (128) - angleData data (128) - W_plrX5 data (128) - W_plrY5 data (128) - angleQ1 data (64) - radiusQ1 data (64) - W_plrX6 data (64) - W_plrY6 data (64) - Packages - WMDataBase - - PolarGraphs - - - - - - -Load a packed experiment without ignoring unknown records: - ->>> d = igor.load(path, ignore_unknown=False) ->>> print(d.format()) -root - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - radiusData data (128) - angleData data (128) - W_plrX5 data (128) - W_plrY5 data (128) - angleQ1 data (64) - radiusQ1 data (64) - W_plrX6 data (64) - W_plrY6 data (64) - Packages - WMDataBase - - PolarGraphs - - - - - - -Try to load a binary wave: - ->>> path = data_path('mac-double.ibw') ->>> d = igor.load(path) -Traceback (most recent call last): - ... -IOError: final record too long; bad pxp file? -""" - -import os.path - -from igor import LOG - - -_this_dir = os.path.dirname(__file__) -_data_dir = os.path.join(_this_dir, 'data') - -def data_path(filename): - LOG.info('Testing igorpy compatibility {}\n'.format(filename)) - path = os.path.join(_data_dir, filename) - return path diff --git a/test/test.py b/test/test.py deleted file mode 100644 index 1b40812..0000000 --- a/test/test.py +++ /dev/null @@ -1,1500 +0,0 @@ -# Copyright (C) 2012-2015 W. Trevor King -# -# This file is part of %(project)s. -# -# %(project)s is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# %(project)s is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with %(project)s. If not, see . - -r"""Test the igor module by loading sample files. - ->>> dumpibw('mac-double.ibw') # doctest: +REPORT_UDIFF -{'version': 2, - 'wave': {'bin_header': {'checksum': 25137, - 'noteSize': 0, - 'pictSize': 0, - 'wfmSize': 166}, - 'note': '', - 'padding': array([], dtype=float64), - 'wData': array([ 5., 4., 3., 2., 1.]), - 'wave_header': {'aModified': 0, - 'bname': 'double', - 'botFullScale': 0.0, - 'creationDate': 3001587842, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001587842, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 4, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} - ->>> dumpibw('mac-textWave.ibw') # doctest: +REPORT_UDIFF -{'version': 5, - 'wave': {'bin_header': {'checksum': 5554, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 0, - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 20, - 'wfmSize': 338}, - 'data_units': '', - 'dimension_units': '', - 'formula': '', - 'labels': [[], [], [], []], - 'note': '', - 'sIndices': array([ 4, 7, 8, 14, 18]), - 'wData': array(['Mary', 'had', 'a', 'little', 'lamb'], - dtype='|S6'), - 'wave_header': {'aModified': 0, - 'bname': 'text0', - 'botFullScale': 0.0, - 'creationDate': 3001571199, - 'dFolder': 69554896, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 22, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001571215, - 'nDim': array([5, 0, 0, 0]), - 'next': 0, - 'npnts': 5, - 'sIndices': 69557296, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': 0, - 'useBits': '\x00', - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} - ->>> dumpibw('mac-version2.ibw') # doctest: +REPORT_UDIFF -{'version': 2, - 'wave': {'bin_header': {'checksum': -16803, - 'noteSize': 15, - 'pictSize': 0, - 'wfmSize': 146}, - 'note': 'This is a test.', - 'padding': array([], dtype=float64), - 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'version2', - 'botFullScale': 0.0, - 'creationDate': 3001251979, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001573594, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} - ->>> dumpibw('mac-version3Dependent.ibw') # doctest: +REPORT_UDIFF -{'version': 3, - 'wave': {'bin_header': {'checksum': -32334, - 'formulaSize': 4, - 'noteSize': 0, - 'pictSize': 0, - 'wfmSize': 126}, - 'formula': ' K0', - 'note': '', - 'padding': array([], dtype=float64), - 'wData': array([], dtype=float32), - 'wave_header': {'aModified': 3, - 'bname': 'version3Dependent', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 23, - 'fileName': 0, - 'formula': 103408364, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001672861, - 'next': 0, - 'npnts': 10, - 'srcFldr': 0, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 1, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} - ->>> dumpibw('mac-version5.ibw') # doctest: +REPORT_UDIFF -{'version': 5, - 'wave': {'bin_header': {'checksum': -12033, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([64, 0, 0, 0]), - 'formulaSize': 0, - 'noteSize': 15, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 340}, - 'data_units': '', - 'dimension_units': '', - 'formula': '', - 'labels': [['', 'Column0'], [], [], []], - 'note': 'This is a test.', - 'sIndices': array([], dtype=float64), - 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'version5', - 'botFullScale': 0.0, - 'creationDate': 3001252180, - 'dFolder': 69554896, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 27, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([69554136, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 69554292, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573601, - 'nDim': array([5, 0, 0, 0]), - 'next': 69555212, - 'npnts': 5, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -32349, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'waveNoteH': 69554032, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} - ->>> dumpibw('mac-zeroPointWave.ibw') # doctest: +REPORT_UDIFF -{'version': 5, - 'wave': {'bin_header': {'checksum': -15649, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 0, - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 320}, - 'data_units': '', - 'dimension_units': '', - 'formula': '', - 'labels': [[], [], [], []], - 'note': '', - 'sIndices': array([], dtype=float64), - 'wData': array([], dtype=float32), - 'wave_header': {'aModified': 3, - 'bname': 'zeroWave', - 'botFullScale': 0.0, - 'creationDate': 3001573964, - 'dFolder': 69554896, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 29, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573964, - 'nDim': array([0, 0, 0, 0]), - 'next': 0, - 'npnts': 0, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 1, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} - ->>> dumpibw('win-double.ibw') # doctest: +REPORT_UDIFF -{'version': 2, - 'wave': {'bin_header': {'checksum': 28962, - 'noteSize': 0, - 'pictSize': 0, - 'wfmSize': 166}, - 'note': '', - 'padding': array([], dtype=float64), - 'wData': array([ 5., 4., 3., 2., 1.]), - 'wave_header': {'aModified': 0, - 'bname': 'double', - 'botFullScale': 0.0, - 'creationDate': 3001587842, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001587842, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 4, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} - ->>> dumpibw('win-textWave.ibw') # doctest: +REPORT_UDIFF -{'version': 5, - 'wave': {'bin_header': {'checksum': 184, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 0, - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 20, - 'wfmSize': 338}, - 'data_units': '', - 'dimension_units': '', - 'formula': '', - 'labels': [[], [], [], []], - 'note': '', - 'sIndices': array([ 4, 7, 8, 14, 18]), - 'wData': array(['Mary', 'had', 'a', 'little', 'lamb'], - dtype='|S6'), - 'wave_header': {'aModified': 0, - 'bname': 'text0', - 'botFullScale': 0.0, - 'creationDate': 3001571199, - 'dFolder': 8108612, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 32, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 7814472, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001571215, - 'nDim': array([5, 0, 0, 0]), - 'next': 0, - 'npnts': 5, - 'sIndices': 8133100, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -1007, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 0, - 'useBits': '\x00', - 'wModified': 1, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} - ->>> dumpibw('win-version2.ibw') # doctest: +REPORT_UDIFF -{'version': 2, - 'wave': {'bin_header': {'checksum': 1047, - 'noteSize': 15, - 'pictSize': 0, - 'wfmSize': 146}, - 'note': 'This is a test.', - 'padding': array([], dtype=float64), - 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'version2', - 'botFullScale': 0.0, - 'creationDate': 3001251979, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001573594, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} - ->>> dumpibw('win-version5.ibw') # doctest: +REPORT_UDIFF -{'version': 5, - 'wave': {'bin_header': {'checksum': 13214, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([64, 0, 0, 0]), - 'formulaSize': 0, - 'noteSize': 15, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 340}, - 'data_units': '', - 'dimension_units': '', - 'formula': '', - 'labels': [['', 'Column0'], [], [], []], - 'note': 'This is a test.', - 'sIndices': array([], dtype=float64), - 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'version5', - 'botFullScale': 0.0, - 'creationDate': 3001252180, - 'dFolder': 8108612, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 30, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([8138784, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 8131824, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573601, - 'nDim': array([5, 0, 0, 0]), - 'next': 8125236, - 'npnts': 5, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -1007, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 1, - 'waveNoteH': 8131596, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} - ->>> dumpibw('win-zeroPointWave.ibw') # doctest: +REPORT_UDIFF -{'version': 5, - 'wave': {'bin_header': {'checksum': 27541, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 0, - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 320}, - 'data_units': '', - 'dimension_units': '', - 'formula': '', - 'labels': [[], [], [], []], - 'note': '', - 'sIndices': array([], dtype=float64), - 'wData': array([], dtype=float32), - 'wave_header': {'aModified': 3, - 'bname': 'zeroWave', - 'botFullScale': 0.0, - 'creationDate': 3001573964, - 'dFolder': 8108612, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 31, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 8125252, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573964, - 'nDim': array([0, 0, 0, 0]), - 'next': 8133140, - 'npnts': 0, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -1007, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 1, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} - ->>> dumppxp('polar-graphs-demo.pxp') # doctest: +REPORT_UDIFF, +ELLIPSIS -record 0: - -record 1: - -record 2: - -record 3: - -record 4: - -record 5: - -record 6: - -record 7: - -record 8: - -record 9: - -record 10: - -record 11: - -record 12: - -record 13: - -record 14: - -record 15: - -record 16: - -record 17: - -record 18: - -record 19: - -record 20: - -record 21: - -record 22: - -record 23: - -record 24: - -record 25: - -record 26: - -record 27: - -record 28: - -record 29: - -record 30: -{'variables': {'sysVars': {'K0': 0.0, - 'K1': 0.0, - 'K10': 0.0, - 'K11': 0.0, - 'K12': 0.0, - 'K13': 0.0, - 'K14': 0.0, - 'K15': 0.0, - 'K16': 0.0, - 'K17': 0.0, - 'K18': 0.0, - 'K19': 0.0, - 'K2': 0.0, - 'K20': 128.0, - 'K3': 0.0, - 'K4': 0.0, - 'K5': 0.0, - 'K6': 0.0, - 'K7': 0.0, - 'K8': 0.0, - 'K9': 0.0}, - 'userStrs': {}, - 'userVars': {}, - 'var_header': {'numSysVars': 21, - 'numUserStrs': 0, - 'numUserVars': 0}}, - 'version': 1} -record 31: -'\x95 Polar Graphs Demo, v3.01\n\n' -record 32: -{'version': 2, - 'wave': {'bin_header': {'checksum': -25004, - 'noteSize': 0, - 'pictSize': 0, - 'wfmSize': 638}, - 'note': '', - 'padding': array([], dtype=float64), - 'wData': array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349, 1.13573945, - 1.24475539, 1.2962544 , 1.28710103, 1.21785283, 1.09272552, - 0.91933674, 0.7082426 , 0.47229454, 0.22585714, -0.01606643, - -0.23874778, -0.42862982, -0.57415301, -0.6664573 , -0.69992352, - -0.67251408, -0.58589762, -0.44534767, -0.25942117, -0.03943586, - 0.20121357, 0.44787762, 0.68553883, 0.89972788, 1.0774051 , - 1.20775461, 1.28283918, 1.29808831, 1.25257373, 1.14906585, - 0.99386656, 0.79642528, 0.56876069, 0.32473388, 0.07920124, - -0.15288824, -0.35740662, -0.52190179, -0.63635898, -0.69381076, - -0.69075894, -0.62739003, -0.5075599 , -0.3385666 , -0.13069656, - 0.10339352, 0.34945396, 0.59250361, 0.81774551, 1.01146686, - 1.16187334, 1.25980926, 1.29931164, 1.27797604, 1.1971004 , - 1.06160903, 0.87975079, 0.66259789, 0.42336911, 0.17663053, - -0.06259823, -0.2797519 , -0.46160996, -0.59710097, -0.67797607, - -0.69931161, -0.65980917, -0.56187314, -0.41146588, -0.21774435, - 0.00749773, 0.25054744, 0.49660596, 0.7306987 , 0.93856692, - 1.10756063, 1.22738981, 1.29075909, 1.29381061, 1.23635852, - 1.1219027 , 0.95740634, 0.7528879 , 0.52079749, 0.2752648 , - 0.03123802, -0.19642642, -0.39386547, -0.54906607, -0.6525743 , - -0.69808841, -0.68283898, -0.60775399, -0.47740453, -0.29972947, - -0.08553842, 0.15212469, 0.39878684, 0.63943672, 0.85942155, - 1.04534864, 1.18589854, 1.2725141 , 1.29992342, 1.2664578 , - 1.17415261, 1.0286293 , 0.83874667, 0.61606491, 0.37414294, - 0.12770344, -0.1082412 , -0.31933719, -0.49272597, -0.61785328, - -0.6871013 , -0.69625437, -0.64475471, -0.53574032, -0.37584305, - -0.17479956, 0.05514668, 0.30000135], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'radiusData', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 0.04908738521234052, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845545774, - 'next': 0, - 'npnts': 128, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} -record 33: -{'version': 2, - 'wave': {'bin_header': {'checksum': 28621, - 'noteSize': 0, - 'pictSize': 0, - 'wfmSize': 638}, - 'note': '', - 'padding': array([], dtype=float64), - 'wData': array([ 0. , 0.0494739 , 0.0989478 , 0.1484217 , 0.1978956 , - 0.24736951, 0.29684341, 0.34631732, 0.3957912 , 0.44526511, - 0.49473903, 0.54421294, 0.59368682, 0.6431607 , 0.69263464, - 0.74210852, 0.79158241, 0.84105635, 0.89053023, 0.94000411, - 0.98947805, 1.03895199, 1.08842587, 1.13789964, 1.18737364, - 1.23684752, 1.2863214 , 1.3357954 , 1.38526928, 1.43474305, - 1.48421705, 1.53369093, 1.58316481, 1.63263881, 1.68211269, - 1.73158658, 1.78106046, 1.83053434, 1.88000822, 1.92948222, - 1.9789561 , 2.02842999, 2.07790399, 2.12737775, 2.17685175, - 2.22632551, 2.27579927, 2.32527351, 2.37474728, 2.42422128, - 2.47369504, 2.52316904, 2.5726428 , 2.6221168 , 2.67159081, - 2.72106457, 2.77053857, 2.82001233, 2.86948609, 2.91896009, - 2.9684341 , 3.0179081 , 3.06738186, 3.11685586, 3.16632962, - 3.21580338, 3.26527762, 3.31475139, 3.36422539, 3.41369915, - 3.46317315, 3.51264691, 3.56212091, 3.61159492, 3.66106868, - 3.71054268, 3.76001644, 3.8094902 , 3.85896444, 3.90843821, - 3.95791221, 4.00738621, 4.05685997, 4.10633373, 4.15580797, - 4.20528126, 4.2547555 , 4.30422926, 4.3537035 , 4.40317726, - 4.45265102, 4.50212526, 4.55159855, 4.60107279, 4.65054703, - 4.70002079, 4.74949455, 4.79896832, 4.84844255, 4.89791584, - 4.94739008, 4.99686432, 5.04633808, 5.09581184, 5.14528561, - 5.19475985, 5.24423361, 5.29370737, 5.34318161, 5.3926549 , - 5.44212914, 5.4916029 , 5.54107714, 5.5905509 , 5.64002466, - 5.6894989 , 5.73897219, 5.78844643, 5.83792019, 5.88739443, - 5.93686819, 5.98634195, 6.03581619, 6.08528948, 6.13476372, - 6.18423796, 6.23371172, 6.28318548], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'angleData', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 0.04908738521234052, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845470039, - 'next': 0, - 'npnts': 128, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} -record 34: -{'version': 5, - 'wave': {'bin_header': {'checksum': 23021, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 80, - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 832}, - 'data_units': '', - 'dimension_units': '', - 'formula': ' PolarRadiusFunction(radiusData,1,0) * cos(PolarAngleFunction(angleData,3,1,2))', - 'labels': [[], [], [], []], - 'note': '', - 'sIndices': array([], dtype=float64), - 'wData': array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, - 1.44305170e-01, 2.23293692e-01, 3.04783821e-01, - 3.79158467e-01, 4.36888516e-01, 4.69528973e-01, - 4.70633775e-01, 4.36502904e-01, 3.66688997e-01, - 2.64211357e-01, 1.35452762e-01, -1.02594923e-02, - -1.61356136e-01, -3.04955602e-01, -4.27943677e-01, - -5.18107474e-01, -5.65230608e-01, -5.62046587e-01, - -5.04969478e-01, -3.94532531e-01, -2.35490710e-01, - -3.65724117e-02, 1.90097600e-01, 4.29877043e-01, - 6.66696191e-01, 8.84287775e-01, 1.06744885e+00, - 1.20323074e+00, 1.28195620e+00, 1.29798901e+00, - 1.25017929e+00, 1.14195395e+00, 9.81046736e-01, - 7.78884649e-01, 5.49682915e-01, 3.09332967e-01, - 7.41607845e-02, -1.40328899e-01, -3.20629656e-01, - -4.56221938e-01, -5.40310800e-01, -5.70244014e-01, - -5.47582209e-01, -4.77826297e-01, -3.69823217e-01, - -2.34920204e-01, -8.59207287e-02, 6.40354082e-02, - 2.02596441e-01, 3.19209903e-01, 4.05949473e-01, - 4.58081126e-01, 4.74326164e-01, 4.56804305e-01, - 4.10668582e-01, 3.43470216e-01, 2.64317334e-01, - 1.82909429e-01, 1.08534366e-01, 4.91267964e-02, - 1.04717268e-02, -4.36885841e-03, 4.64119762e-03, - 3.45129520e-02, 7.95329511e-02, 1.31838784e-01, - 1.82213545e-01, 2.21028924e-01, 2.39245579e-01, - 2.29380637e-01, 1.86348081e-01, 1.08093813e-01, - -4.03938442e-03, -1.45255283e-01, -3.07566285e-01, - -4.80366081e-01, -6.51240766e-01, -8.07001889e-01, - -9.34792042e-01, -1.02321768e+00, -1.06338477e+00, - -1.04975033e+00, -9.80714381e-01, -8.58889818e-01, - -6.91040277e-01, -4.87653464e-01, -2.62210011e-01, - -3.01902127e-02, 1.92100301e-01, 3.88785005e-01, - 5.45667768e-01, 6.51326835e-01, 6.98035002e-01, - 6.82368934e-01, 6.05477571e-01, 4.72992837e-01, - 2.94585884e-01, 8.31873119e-02, -1.46010652e-01, - -3.76755983e-01, -5.93006968e-01, -7.80143738e-01, - -9.26071882e-01, -1.02209401e+00, -1.06349015e+00, - -1.04976654e+00, -9.84551251e-01, -8.75151932e-01, - -7.31834948e-01, -5.66861272e-01, -3.93398553e-01, - -2.24383846e-01, -7.14399144e-02, 5.60413450e-02, - 1.51621893e-01, 2.12215677e-01, 2.38205954e-01, - 2.33226836e-01, 2.03656554e-01, 1.57870770e-01, - 1.05330117e-01, 5.55786416e-02, 1.72677450e-02, - -2.72719120e-03, 5.24539061e-08], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'W_plrX5', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 24, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 0, - 'formula': 8054500, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([128, 0, 0, 0]), - 'next': 8054516, - 'npnts': 128, - 'sIndices': 0, - 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} -record 35: -{'version': 5, - 'wave': {'bin_header': {'checksum': -9146, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 80, - 'noteSize': 82, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 832}, - 'data_units': '', - 'dimension_units': '', - 'formula': ' PolarRadiusFunction(radiusData,1,0) * sin(PolarAngleFunction(angleData,3,1,2))', - 'labels': [[], [], [], []], - 'note': 'shadowX=W_plrX5,appendRadius=radiusData,appendAngleData=angleData,angleDataUnits=2', - 'sIndices': array([], dtype=float64), - 'wData': array([ 0.30000001, 0.54418772, 0.77101213, 0.96511477, 1.1135726 , - 1.20686483, 1.23956215, 1.21068466, 1.12370288, 0.98618096, - 0.80910152, 0.60592639, 0.39147732, 0.18073183, -0.01236418, - -0.17596789, -0.30120692, -0.38277394, -0.41920158, -0.41280419, - -0.36929506, -0.29712263, -0.20658807, -0.10882771, -0.01475283, - 0.06595302, 0.12569843, 0.15962352, 0.16596791, 0.14613269, - 0.10443594, 0.04758934, -0.01605497, -0.0774129 , -0.12764584, - -0.15911636, -0.16622847, -0.14607331, -0.09881912, -0.02780312, - 0.06068454, 0.15791172, 0.25346208, 0.33617997, 0.3952153 , - 0.42107204, 0.40657136, 0.34763175, 0.24380288, 0.09848462, - -0.08117689, -0.28473276, -0.49916485, -0.70986813, -0.90179092, - -1.06064332, -1.17407382, -1.23270524, -1.23095524, -1.16755545, - -1.04573321, -0.87303019, -0.66077417, -0.42323959, -0.1765765 , - 0.06242594, 0.2776148 , 0.45470679, 0.58236426, 0.65303123, - 0.66346282, 0.61490625, 0.51291907, 0.36684951, 0.18901938, - -0.00631659, -0.20414437, -0.389898 , -0.55060786, -0.67586488, - -0.75857663, -0.79539269, -0.78681922, -0.73699296, -0.65315133, - -0.54485315, -0.42300734, -0.29883695, -0.18282266, -0.08376524, - -0.00802278, 0.0409977 , 0.06305727, 0.06099379, 0.04033075, - 0.00863387, -0.02533132, -0.05255322, -0.06475239, -0.05528941, - -0.01991711, 0.04269439, 0.13071296, 0.23921135, 0.36052904, - 0.48491719, 0.60139763, 0.69877088, 0.76667541, 0.79660165, - 0.78277934, 0.72283876, 0.6181944 , 0.47410288, 0.29939076, - 0.10585135, -0.09260413, -0.28104633, -0.44468346, -0.57008827, - -0.64630753, -0.66580337, -0.62512833, -0.52528399, -0.37171093, - -0.17394456, 0.0550792 , 0.30000135], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'W_plrY5', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 26, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 0, - 'formula': 8054532, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([128, 0, 0, 0]), - 'next': 8084972, - 'npnts': 128, - 'sIndices': 0, - 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'waveNoteH': 7996608, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} -record 36: -{'version': 2, - 'wave': {'bin_header': {'checksum': 14307, - 'noteSize': 0, - 'pictSize': 0, - 'wfmSize': 382}, - 'note': '', - 'padding': array([], dtype=float64), - 'wData': array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595, 0.32828814, - 0.34491032, 0.36153251, 0.3781547 , 0.39477688, 0.41139907, - 0.42802125, 0.44464344, 0.46126559, 0.47788778, 0.49450997, - 0.51113212, 0.52775431, 0.54437649, 0.56099868, 0.57762086, - 0.59424305, 0.61086524, 0.62748742, 0.64410961, 0.66073179, - 0.67735398, 0.69397616, 0.71059835, 0.72722054, 0.74384272, - 0.76046491, 0.77708709, 0.79370928, 0.81033146, 0.82695365, - 0.84357584, 0.86019802, 0.87682021, 0.89344239, 0.91006458, - 0.92668676, 0.94330889, 0.95993114, 0.97655326, 0.99317551, - 1.00979757, 1.02641988, 1.04304194, 1.05966425, 1.07628632, - 1.09290862, 1.10953069, 1.12615299, 1.14277506, 1.15939736, - 1.17601943, 1.19264174, 1.2092638 , 1.22588611, 1.24250817, - 1.25913048, 1.27575254, 1.29237485, 1.30899692], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'angleQ1', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845473705, - 'next': 0, - 'npnts': 64, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} -record 37: -{'version': 2, - 'wave': {'bin_header': {'checksum': -12080, - 'noteSize': 0, - 'pictSize': 0, - 'wfmSize': 382}, - 'note': '', - 'padding': array([], dtype=float64), - 'wData': array([ -8.34064484, -7.66960144, -6.62294245, -6.82878971, - -8.6383152 , -11.20019722, -13.83398628, -15.95139503, - -16.18096733, -13.58062267, -9.26843071, -5.34649038, - -3.01010084, -2.30953455, -2.73682952, -3.72112942, - -4.85171413, -5.63053226, -5.48626232, -4.49401283, - -3.53216696, -3.34821796, -4.07400894, -5.87675714, - -9.11268425, -12.98700237, -15.06296921, -13.71571922, - -10.23535728, -7.01303005, -5.23288727, -5.71091986, - -9.24852943, -14.06335735, -15.846241 , -12.78800964, - -7.8465519 , -4.56293297, -3.54999399, -3.67789125, - -4.10172844, -4.78980875, -6.20238352, -8.17891598, - -9.2803278 , -8.36780167, -6.3059268 , -4.85605574, - -4.54975414, -4.52917624, -3.99160147, -3.1971693 , - -2.93472862, -3.47230864, -4.7322526 , -6.80173016, - -9.08601665, -10.00928402, -8.87677383, -6.88120317, - -5.61007977, -5.6351161 , -6.41880989, -6.8738699 ], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'radiusQ1', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845473634, - 'next': 0, - 'npnts': 64, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}}} -record 38: -{'version': 5, - 'wave': {'bin_header': {'checksum': -5745, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 78, - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 576}, - 'data_units': '', - 'dimension_units': '', - 'formula': ' PolarRadiusFunction(radiusQ1,1,-40) * cos(PolarAngleFunction(angleQ1,2,2,2))', - 'labels': [[], [], [], []], - 'note': '', - 'sIndices': array([], dtype=float64), - 'wData': array([ 30.58058929, 31.08536911, 31.93481636, 31.57315445, - 29.68683434, 27.10366058, 24.47453499, 22.3495121 , - 21.98692894, 24.21500397, 27.95923996, 31.28394508, - 33.12408066, 33.46794128, 32.79909515, 31.64211464, - 30.36601639, 29.40137291, 29.22361755, 29.74564171, - 30.21624565, 30.02338219, 29.0822773 , 27.28613091, - 24.38687515, 21.04944038, 19.16931915, 19.92274094, - 22.23493385, 24.27418709, 25.1893177 , 24.44671249, - 21.56310272, 17.87704659, 16.35500908, 18.09041786, - 20.97328949, 22.66550255, 22.84443283, 22.29068756, - 21.55643272, 20.67234993, 19.38551521, 17.81604385, - 16.77393341, 16.8293457 , 17.4496479 , 17.6982975 , - 17.34101677, 16.83446693, 16.56042671, 16.38027191, - 15.94310474, 15.16159916, 14.10328865, 12.76812935, - 11.41363049, 10.60795975, 10.52314186, 10.67826462, - 10.5454855 , 9.99268055, 9.22939587, 8.5736742 ], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'W_plrX6', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 30, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 0, - 'formula': 8052116, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([64, 0, 0, 0]), - 'next': 8324392, - 'npnts': 64, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} -record 39: -{'version': 5, - 'wave': {'bin_header': {'checksum': -16604, - 'dataEUnitsSize': 0, - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formulaSize': 78, - 'noteSize': 78, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'wfmSize': 576}, - 'data_units': '', - 'dimension_units': '', - 'formula': ' PolarRadiusFunction(radiusQ1,1,-40) * sin(PolarAngleFunction(angleQ1,2,2,2))', - 'labels': [[], [], [], []], - 'note': 'shadowX=W_plrX6,appendRadius=radiusQ1,appendAngleData=angleQ1,angleDataUnits=2', - 'sIndices': array([], dtype=float64), - 'wData': array([ 8.19404411, 8.88563347, 9.70543861, 10.17177773, - 10.11173058, 9.73756695, 9.25513077, 8.8788929 , - 9.16085339, 10.56489944, 12.75579453, 14.90572262, - 16.46352959, 17.33401871, 17.68511391, 17.74635315, - 17.70048141, 17.79942513, 18.36241531, 19.38741684, - 20.41767311, 21.02259827, 21.09260368, 20.4905529 , - 18.95538521, 16.9299469 , 15.94969368, 17.14490509, - 19.78741264, 22.33615875, 23.96352196, 24.04369545, - 21.92454147, 18.79150391, 17.77407646, 20.32803917, - 24.37140465, 27.24079132, 28.40307808, 28.67787933, - 28.70550728, 28.50283432, 27.68538666, 26.36607552, - 25.73583984, 26.78374672, 28.8236084 , 30.36226463, - 30.91939545, 31.22146797, 31.97431755, 32.95656204, - 33.4611969 , 33.23248672, 32.3250885 , 30.64473915, - 28.72983551, 28.05199242, 29.29024887, 31.3501091 , - 32.7331543 , 32.87995529, 32.28799438, 31.99738503], dtype=float32), - 'wave_header': {'aModified': 0, - 'bname': 'W_plrY6', - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 32, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], - ['', '', '', ''], - ['', '', '', ''], - ['', '', '', '']], - dtype='|S1'), - 'fileName': 0, - 'formula': 7995612, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([64, 0, 0, 0]), - 'next': 0, - 'npnts': 64, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wModified': 0, - 'waveNoteH': 7998208, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}}} -record 40: -'Packages' -record 41: -'WMDataBase' -record 42: -{'variables': {'sysVars': {'K0': 0.0, - 'K1': 0.0, - 'K10': 0.0, - 'K11': 0.0, - 'K12': 0.0, - 'K13': 0.0, - 'K14': 0.0, - 'K15': 0.0, - 'K16': 0.0, - 'K17': 0.0, - 'K18': 0.0, - 'K19': 0.0, - 'K2': 0.0, - 'K20': 128.0, - 'K3': 0.0, - 'K4': 0.0, - 'K5': 0.0, - 'K6': 0.0, - 'K7': 0.0, - 'K8': 0.0, - 'K9': 0.0}, - 'userStrs': {'u_dataBase': ';PolarGraph0:,...,useCircles=2,maxArcLine=6;', - 'u_dbBadStringChars': ',;=:', - 'u_dbCurrBag': 'PolarGraph1', - 'u_dbCurrContents': ',appendRadius=radiusQ1,...,useCircles=2,maxArcLine=6;', - 'u_dbReplaceBadChars': '\xa9\xae\x99\x9f', - 'u_str': '2'}, - 'userVars': {}, - 'var_header': {'numSysVars': 21, - 'numUserStrs': 6, - 'numUserVars': 0}}, - 'version': 1} -record 43: -'' -record 44: -'PolarGraphs' -record 45: -{'variables': {'sysVars': {'K0': 0.0, - 'K1': 0.0, - 'K10': 0.0, - 'K11': 0.0, - 'K12': 0.0, - 'K13': 0.0, - 'K14': 0.0, - 'K15': 0.0, - 'K16': 0.0, - 'K17': 0.0, - 'K18': 0.0, - 'K19': 0.0, - 'K2': 0.0, - 'K20': 128.0, - 'K3': 0.0, - 'K4': 0.0, - 'K5': 0.0, - 'K6': 0.0, - 'K7': 0.0, - 'K8': 0.0, - 'K9': 0.0}, - 'userStrs': {'u_colorList': 'black;blue;green;cyan;red;magenta;yellow;white;special', - 'u_debugStr': 'Turn Debugging On', - 'u_polAngleAxesWherePop': 'Off;Radius Start;Radius End;Radius Start and End;All Major Radii;At Listed Radii', - 'u_polAngleUnitsPop': 'deg;rad', - 'u_polLineStylePop': 'solid;dash 1;dash 2;dash 3;dash 4;dash 5;dash 6;dash 7;dash 8;dash 9;dash 10;dash 11;dash 12;dash 13;dash 14;dash 15;dash 16;dash 17;', - 'u_polOffOn': 'Off;On', - 'u_polRadAxesWherePop': ' Off; Angle Start; Angle Middle; Angle End; Angle Start and End; 0; 90; 180; -90; 0, 90; 90, 180; -180, -90; -90, 0; 0, 180; 90, -90; 0, 90, 180, -90; All Major Angles; At Listed Angles', - 'u_polRotPop': ' -90; 0; +90; +180', - 'u_popup': '', - 'u_prompt': ''}, - 'userVars': {'V_bottom': 232.0, - 'V_left': 1.0, - 'V_max': 2.4158518093414401, - 'V_min': -2.1848498883412, - 'V_right': 232.0, - 'V_top': 1.0, - 'u_UniqWaveNdx': 8.0, - 'u_UniqWinNdx': 3.0, - 'u_angle0': 0.0, - 'u_angleRange': 6.2831853071795862, - 'u_debug': 0.0, - 'u_majorDelta': 0.0, - 'u_numPlaces': 0.0, - 'u_polAngle0': 0.26179938779914941, - 'u_polAngleRange': 1.0471975511965976, - 'u_polInnerRadius': -20.0, - 'u_polMajorAngleInc': 0.26179938779914941, - 'u_polMajorRadiusInc': 10.0, - 'u_polMinorAngleTicks': 3.0, - 'u_polMinorRadiusTicks': 1.0, - 'u_polOuterRadius': 0.0, - 'u_segsPerMinorArc': 3.0, - 'u_tickDelta': 0.0, - 'u_var': 0.0, - 'u_x1': 11.450159535018935, - 'u_x2': 12.079591517721363, - 'u_y1': 42.732577139459856, - 'u_y2': 45.081649278814126}, - 'var_header': {'numSysVars': 21, - 'numUserStrs': 10, - 'numUserVars': 28}}, - 'version': 1} -record 46: -'' -record 47: -'' -record 48: -'| Platform=Windows95, IGORVersion=3.130\n\n\n\nMoveWindow/P 5.25,40.25,504.75,335\n...hook=PolarWindowHook\nEndMacro\n' -record 49: -'' -record 50: -'#include version >= 3.0\n' - -filesystem: -{'root': {'K0': 0.0, - 'K1': 0.0, - 'K10': 0.0, - 'K11': 0.0, - 'K12': 0.0, - 'K13': 0.0, - 'K14': 0.0, - 'K15': 0.0, - 'K16': 0.0, - 'K17': 0.0, - 'K18': 0.0, - 'K19': 0.0, - 'K2': 0.0, - 'K20': 128.0, - 'K3': 0.0, - 'K4': 0.0, - 'K5': 0.0, - 'K6': 0.0, - 'K7': 0.0, - 'K8': 0.0, - 'K9': 0.0, - 'Packages': {'PolarGraphs': {'V_bottom': 232.0, - 'V_left': 1.0, - 'V_max': 2.4158518093414401, - 'V_min': -2.1848498883412, - 'V_right': 232.0, - 'V_top': 1.0, - 'u_UniqWaveNdx': 8.0, - 'u_UniqWinNdx': 3.0, - 'u_angle0': 0.0, - 'u_angleRange': 6.2831853071795862, - 'u_colorList': 'black;blue;green;cyan;red;magenta;yellow;white;special', - 'u_debug': 0.0, - 'u_debugStr': 'Turn Debugging On', - 'u_majorDelta': 0.0, - 'u_numPlaces': 0.0, - 'u_polAngle0': 0.26179938779914941, - 'u_polAngleAxesWherePop': 'Off;Radius Start;Radius End;Radius Start and End;All Major Radii;At Listed Radii', - 'u_polAngleRange': 1.0471975511965976, - 'u_polAngleUnitsPop': 'deg;rad', - 'u_polInnerRadius': -20.0, - 'u_polLineStylePop': 'solid;dash 1;dash 2;dash 3;dash 4;dash 5;dash 6;dash 7;dash 8;dash 9;dash 10;dash 11;dash 12;dash 13;dash 14;dash 15;dash 16;dash 17;', - 'u_polMajorAngleInc': 0.26179938779914941, - 'u_polMajorRadiusInc': 10.0, - 'u_polMinorAngleTicks': 3.0, - 'u_polMinorRadiusTicks': 1.0, - 'u_polOffOn': 'Off;On', - 'u_polOuterRadius': 0.0, - 'u_polRadAxesWherePop': ' Off; Angle Start; Angle Middle; Angle End; Angle Start and End; 0; 90; 180; -90; 0, 90; 90, 180; -180, -90; -90, 0; 0, 180; 90, -90; 0, 90, 180, -90; All Major Angles; At Listed Angles', - 'u_polRotPop': ' -90; 0; +90; +180', - 'u_popup': '', - 'u_prompt': '', - 'u_segsPerMinorArc': 3.0, - 'u_tickDelta': 0.0, - 'u_var': 0.0, - 'u_x1': 11.450159535018935, - 'u_x2': 12.079591517721363, - 'u_y1': 42.732577139459856, - 'u_y2': 45.081649278814126}, - 'WMDataBase': {'u_dataBase': ';PolarGraph0:,appendRadius=radiusData,...,useCircles=2,maxArcLine=6;', - 'u_dbBadStringChars': ',;=:', - 'u_dbCurrBag': 'PolarGraph1', - 'u_dbCurrContents': ',appendRadius=radiusQ1,...,useCircles=2,maxArcLine=6;', - 'u_dbReplaceBadChars': '\xa9\xae\x99\x9f', - 'u_str': '2'}}, - 'W_plrX5': , - 'W_plrX6': , - 'W_plrY5': , - 'W_plrY6': , - 'angleData': , - 'angleQ1': , - 'radiusData': , - 'radiusQ1': }} - -walking filesystem: -walk callback on ([], root, {'K0': 0.0,...}) -walk callback on (['root'], K0, 0.0) -walk callback on (['root'], K1, 0.0) -walk callback on (['root'], K10, 0.0) -... -walk callback on (['root'], K9, 0.0) -walk callback on (['root'], Packages, {'PolarGraphs': ...}) -walk callback on (['root', 'Packages'], PolarGraphs, {...}) -walk callback on (['root', 'Packages', 'PolarGraphs'], V_bottom, 232.0) -... -walk callback on (['root', 'Packages'], WMDataBase, {...}) -... -walk callback on (['root'], radiusQ1, ) -""" - -import os.path -from pprint import pformat - -from igor import LOG -from igor.binarywave import load as loadibw -from igor.packed import load as loadpxp -from igor.packed import walk as _walk -from igor.record.base import TextRecord -from igor.record.folder import FolderStartRecord, FolderEndRecord -from igor.record.variables import VariablesRecord -from igor.record.wave import WaveRecord - - -_this_dir = os.path.dirname(__file__) -_data_dir = os.path.join(_this_dir, 'data') - -def dumpibw(filename): - LOG.info('Testing {}\n'.format(filename)) - path = os.path.join(_data_dir, filename) - data = loadibw(path) - pprint(data) - -def walk_callback(dirpath, key, value): - print('walk callback on ({}, {}, {})'.format( - dirpath, key, pformat(value))) - -def dumppxp(filename, walk=True): - LOG.info('Testing {}\n'.format(filename)) - path = os.path.join(_data_dir, filename) - records,filesystem = loadpxp(path) - for i,record in enumerate(records): - print('record {}:'.format(i)) - if isinstance(record, (FolderStartRecord, FolderEndRecord)): - pprint(record.null_terminated_text) - elif isinstance(record, TextRecord): - pprint(record.text) - elif isinstance(record, VariablesRecord): - pprint(record.variables) - elif isinstance(record, WaveRecord): - pprint(record.wave) - else: - pprint(record) - print('\nfilesystem:') - pprint(filesystem) - if walk: - print('\nwalking filesystem:') - _walk(filesystem, walk_callback) - -def pprint(data): - lines = pformat(data).splitlines() - print('\n'.join([line.rstrip() for line in lines])) diff --git a/test/test_windows.py b/test/test_windows.py new file mode 100644 index 0000000..324f53d --- /dev/null +++ b/test/test_windows.py @@ -0,0 +1,30 @@ +from pathlib import Path +from pygor.binarywave import load_ibw +from pygor import load + +TEST_DATA_DIRECTORY = Path(__file__).parent / "data" + + +def test_windows_ibw(): + try: + igor_waves = [ + load_ibw(str(TEST_DATA_DIRECTORY / filename)) + for filename in [ + "win-double.ibw", + "win-textWave.ibw", + "win-version2.ibw", + "win-version5.ibw", + "win-zeroPointWave.ibw", + ] + ] + assert True + except Exception: + assert False, "Loading Igor Binary Waves ran into an exception." + + +def test_load_pxp(): + try: + load(str(TEST_DATA_DIRECTORY / "polar-graphs-demo.pxp")) + assert True + except Exception: + assert False, "Loading PXP ran into an exception." diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..f67b52c --- /dev/null +++ b/uv.lock @@ -0,0 +1,120 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348 }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362 }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103 }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382 }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462 }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618 }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511 }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783 }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506 }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190 }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828 }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006 }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765 }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736 }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719 }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072 }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213 }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632 }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532 }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885 }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467 }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144 }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217 }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014 }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935 }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122 }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143 }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260 }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225 }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pygor" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "numpy" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "numpy", specifier = ">=2.0.0" }] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.4.1" }] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, +]