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 },
+]