mirror of
https://github.com/wassname/segpy.git
synced 2026-08-12 12:30:30 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9b0d7903c | ||
|
|
d604e328ef | ||
|
|
2460cc09ae | ||
|
|
fde4d3add0 | ||
|
|
82e02dc7f2 | ||
|
|
f94301a951 | ||
|
|
d29d984b58 | ||
|
|
18434779e3 | ||
|
|
f851958d4c | ||
|
|
91562fddfd | ||
|
|
47361906b1 | ||
|
|
a565f4d245 | ||
|
|
b053d5d5f3 | ||
|
|
9b80ce49de | ||
|
|
c7209f909a | ||
|
|
2c5774c0b4 | ||
|
|
965b9482da | ||
|
|
2bee1e4f99 | ||
|
|
9652730586 | ||
|
|
f792029829 | ||
|
|
480bb024be | ||
|
|
a3ecf62a09 | ||
|
|
a7d658995a | ||
|
|
5312cca052 | ||
|
|
042b415ca1 | ||
|
|
c4703af67a | ||
|
|
e5d409eb93 | ||
|
|
7f50044b43 | ||
|
|
ebb62fbc7b | ||
|
|
ee9f2cbb52 | ||
|
|
6d986a21a9 | ||
|
|
23ee609c50 | ||
|
|
f89de23b82 | ||
|
|
d06c2918d3 | ||
|
|
28046a19e2 | ||
|
|
3ff9308d02 | ||
|
|
986bae3da9 | ||
|
|
60c66a249e | ||
|
|
df312035e9 | ||
|
|
3b740888bd | ||
|
|
39988a2ca0 | ||
|
|
506ce10581 | ||
|
|
2049e89819 | ||
|
|
80966a18fb | ||
|
|
db997625e3 | ||
|
|
af1f6de5d9 | ||
|
|
b8cd3f696d | ||
|
|
61de7cd172 | ||
|
|
e657342126 | ||
|
|
eaf79b0e68 | ||
|
|
6158cf3279 | ||
|
|
40bfb76f29 | ||
|
|
d257ced760 | ||
|
|
9c880b6715 | ||
|
|
5e33d2731c | ||
|
|
9f090c2381 | ||
|
|
ce08d5b99a | ||
|
|
d0a1eb5a47 | ||
|
|
5bfba47daf | ||
|
|
a4e4e78ee8 | ||
|
|
c1b2dfed86 | ||
|
|
917e0a1ebe | ||
|
|
9ebb5c7477 | ||
|
|
d57dcef777 | ||
|
|
c713475e07 | ||
|
|
4c222b96a6 | ||
|
|
f491fe80b1 | ||
|
|
8c484a1ffa | ||
|
|
2a22e8588f | ||
|
|
1ba97f75ce | ||
|
|
b042133f55 | ||
|
|
71ad80b016 | ||
|
|
0c0096f314 | ||
|
|
b4e98a562f | ||
|
|
fa5371349d | ||
|
|
32eeb6e2f3 | ||
|
|
c839a49531 | ||
|
|
68c5289a91 | ||
|
|
9dbc9d15dc | ||
|
|
89a8b2420f | ||
|
|
b52ea4cff6 | ||
|
|
2e9064432c | ||
|
|
edd1fce166 | ||
|
|
3269e13705 | ||
|
|
449fecd757 | ||
|
|
a50c947a95 | ||
|
|
90320f6a42 | ||
|
|
b0e02e2f8e | ||
|
|
05ecd3423e | ||
|
|
fd35fd4cd6 | ||
|
|
fab758f9fc | ||
|
|
3f82865c2d | ||
|
|
002385fc84 | ||
|
|
0801fc2c17 | ||
|
|
b5af594716 | ||
|
|
abf093bce2 | ||
|
|
05b1352730 | ||
|
|
b0243c984e | ||
|
|
caa90a18ed | ||
|
|
cdfb0c9088 | ||
|
|
d44a4d60ae | ||
|
|
44d6f4e3d9 | ||
|
|
d8e563deb0 | ||
|
|
3449a81b80 | ||
|
|
71e77b0ef3 | ||
|
|
1bcdf29f0c | ||
|
|
aab5ebb81b | ||
|
|
a80a4282ac | ||
|
|
9e1921e4b6 | ||
|
|
b8058578d6 | ||
|
|
e25c9682d8 | ||
|
|
5c6238c528 | ||
|
|
442444b0f1 | ||
|
|
b90ff07940 | ||
|
|
6e3494586a | ||
|
|
60acf5f3db | ||
|
|
c60ee01796 | ||
|
|
0ec2c192a3 | ||
|
|
2b7351848b | ||
|
|
f0bd88ffe8 | ||
|
|
bdfb0aaf86 | ||
|
|
e8cdaaf198 | ||
|
|
1311d2f84f | ||
|
|
cf3b6f1f70 | ||
|
|
7da4f755da | ||
|
|
5cfc1805e9 | ||
|
|
25d2387d39 | ||
|
|
283517219d | ||
|
|
d7e531dee8 | ||
|
|
39e31afa28 | ||
|
|
5ab4c70969 | ||
|
|
90574122d5 | ||
|
|
e57cb19f83 | ||
|
|
deb9d6c8ca | ||
|
|
88987a8755 | ||
|
|
dd24ea6bd8 | ||
|
|
ff40995995 | ||
|
|
8a5c2997b9 | ||
|
|
b4b0fe5d92 | ||
|
|
52e2967ec2 | ||
|
|
1779f11b44 | ||
|
|
b026077e09 | ||
|
|
b6fda775c2 | ||
|
|
9751fa6300 | ||
|
|
e6ed86dc8d | ||
|
|
e938be8507 | ||
|
|
46ddafb37c | ||
|
|
17a6475e5b | ||
|
|
b1782b9c81 | ||
|
|
7d2079de83 | ||
|
|
aac598d64f | ||
|
|
19c89d9029 | ||
|
|
52cb3a7005 | ||
|
|
3fd47ac7e5 | ||
|
|
60e4295b29 | ||
|
|
7c7b771efb | ||
|
|
8f4b79e906 | ||
|
|
35678d9ec9 | ||
|
|
469832416e | ||
|
|
1e4494298e | ||
|
|
b91dd65d82 | ||
|
|
1d20aaf820 | ||
|
|
78bae6f859 | ||
|
|
6e8843dce6 | ||
|
|
99b37024ab | ||
|
|
77d5ffb10f | ||
|
|
4862f347df | ||
|
|
38511e67ba | ||
|
|
c9e9e7c599 | ||
|
|
842e16f591 | ||
|
|
955eedcf94 | ||
|
|
0d61bc782e | ||
|
|
d454f2b4eb | ||
|
|
5322c2a5d2 | ||
|
|
a152672bf5 | ||
|
|
797cfee020 | ||
|
|
5a1f790ffd | ||
|
|
2d13fc8c82 | ||
|
|
f1310459d7 | ||
|
|
c04cf0901f | ||
|
|
e56eefbd2c | ||
|
|
f8c298a971 | ||
|
|
e74f4498aa | ||
|
|
ee1e05a9ff | ||
|
|
81728459e7 | ||
|
|
7bad88ce57 | ||
|
|
2e342e5277 | ||
|
|
ccbedfa150 | ||
|
|
255a490914 | ||
|
|
8c1fe0742d | ||
|
|
2709785169 | ||
|
|
9444567b38 | ||
|
|
0f5db22d41 | ||
|
|
246b8515e4 | ||
|
|
8f35206733 | ||
|
|
1fd7a5d4c6 | ||
|
|
91e325c95a | ||
|
|
2ee69544de | ||
|
|
6f3aba6a4d | ||
|
|
905ec4fe39 | ||
|
|
ceb0f9bd61 | ||
|
|
d2f1beac9d | ||
|
|
d50dc0b097 | ||
|
|
ef58b37e6c | ||
|
|
27d78b6b6a | ||
|
|
7ac0083a52 | ||
|
|
ee228e2dc9 | ||
|
|
1abff28a40 | ||
|
|
ddf4f2f6ab | ||
|
|
dfb92978f3 | ||
|
|
fd0de79d18 | ||
|
|
8eaea4b066 | ||
|
|
b0fe25b809 | ||
|
|
5f1bf53992 | ||
|
|
9ddae466dd | ||
|
|
bcadf4478a | ||
|
|
8b9931cd84 | ||
|
|
81f55a3058 | ||
|
|
8f6e0daf30 | ||
|
|
b4800551fd | ||
|
|
e02b7be182 | ||
|
|
dcac37022d | ||
|
|
3b8f3ac26c | ||
|
|
c6ebe9f3d6 | ||
|
|
3a885b93ca | ||
|
|
8e08508ac6 | ||
|
|
e64ec24bf7 | ||
|
|
b816f7787b | ||
|
|
283070d491 | ||
|
|
b52aae71e4 | ||
|
|
24eaa56908 | ||
|
|
2e00902ed0 | ||
|
|
eb47898ae3 | ||
|
|
7e40a26196 | ||
|
|
15c9bf81c4 | ||
|
|
4cb5cf539a | ||
|
|
7014fba323 | ||
|
|
662f7c67bb | ||
|
|
1c19444946 | ||
|
|
24da1dd6cb | ||
|
|
1767538b5e | ||
|
|
cabe8d71e1 | ||
|
|
99f65c9c8e | ||
|
|
701c75f926 | ||
|
|
c7eb00264e | ||
|
|
34df01b65d | ||
|
|
4b1b4ce3e0 | ||
|
|
89cb5a5d28 | ||
|
|
3eb58caae4 | ||
|
|
429d0b8b24 | ||
|
|
3d236ed96b | ||
|
|
31a88eb4b3 | ||
|
|
48d0b7b684 | ||
|
|
6b0399a5a2 | ||
|
|
00f1347658 | ||
|
|
f9639100a6 | ||
|
|
9adcc9bd79 | ||
|
|
411453b765 | ||
|
|
5a359c8a1f | ||
|
|
9af1c27131 | ||
|
|
d4831516f3 | ||
|
|
df6edc96aa | ||
|
|
35b987ea21 | ||
|
|
6ccf6bac71 | ||
|
|
865994b1ad | ||
|
|
d5f3feff2e | ||
|
|
e1225e4ab6 | ||
|
|
a62be5d2f5 | ||
|
|
0d5adb96cc | ||
|
|
687c0096ab | ||
|
|
fc4e21ee38 | ||
|
|
28e20cd055 | ||
|
|
e66d3ec210 | ||
|
|
4150208569 | ||
|
|
92ae363eb5 | ||
|
|
98c1ba1385 | ||
|
|
af098c9950 | ||
|
|
0615ce8fc9 | ||
|
|
a7842ab28b | ||
|
|
deaa39982f | ||
|
|
d8bcd286a4 | ||
|
|
7fa753d695 | ||
|
|
7b57e945a5 | ||
|
|
2fceed9e1a | ||
|
|
c555ded3c5 | ||
|
|
c7631981dc | ||
|
|
c6c5a64055 | ||
|
|
aef29e7a1f | ||
|
|
53b94d1e7b | ||
|
|
6b67bb64ba | ||
|
|
9bf2afa3da | ||
|
|
881acb4abe | ||
|
|
25ac9f7201 | ||
|
|
60dd2ed06e | ||
|
|
6556b5900c | ||
|
|
91ce42ea98 | ||
|
|
bce3bcb69b | ||
|
|
95e73fed91 | ||
|
|
cadabf955b | ||
|
|
f8dc5eee79 | ||
|
|
221cee74e9 | ||
|
|
cf3154a61e | ||
|
|
0dc603820f | ||
|
|
25b2371ae1 | ||
|
|
1b53f5b0f1 | ||
|
|
62ddbfe136 | ||
|
|
970b093dfe | ||
|
|
bbb2a9e090 | ||
|
|
23446f6e81 | ||
|
|
f3e0e86eb5 | ||
|
|
354442f780 | ||
|
|
0c8cb9a95b | ||
|
|
3b7a1ec421 | ||
|
|
a645b0ea28 | ||
|
|
125ffa670f | ||
|
|
b617bb8975 | ||
|
|
fa56e5b641 | ||
|
|
bc8ec143ed | ||
|
|
75e154c001 | ||
|
|
0e49003e36 | ||
|
|
3636e8661c | ||
|
|
511b531837 |
+64
@@ -0,0 +1,64 @@
|
||||
# Data
|
||||
data/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
.hypothesis
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
bin/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# Rope
|
||||
.ropeproject
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
*.pot
|
||||
|
||||
# Sphinx documentation
|
||||
docs/build/
|
||||
|
||||
# Event stores
|
||||
*.events
|
||||
@@ -0,0 +1,9 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
# command to install dependencies
|
||||
install:
|
||||
- "pip install -r test/test-requirements.txt"
|
||||
# command to run tests
|
||||
script: nosetests
|
||||
@@ -0,0 +1,102 @@
|
||||
=================
|
||||
How to contribute
|
||||
=================
|
||||
|
||||
Third-party patches are welcomed for improving Segpy. We simply can't
|
||||
access the huge variety of SEG Y files produced by a myriad of other
|
||||
systems.
|
||||
|
||||
Although we want to keep it as easy as possible to contribute changes that
|
||||
get things working in your environment, there are a few guidelines that we
|
||||
need contributors to follow so that we can have a chance of keeping on
|
||||
top of things.
|
||||
|
||||
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
The easiest way to help is by submitting issues reporting defects or
|
||||
requesting additional features.
|
||||
|
||||
* Make sure you have a `GitHub account <https://github.com/signup/free>`_
|
||||
* Submit an issue, assuming one does not already exist.
|
||||
* Clearly describe the issue including steps to reproduce when it is a bug.
|
||||
* Make sure you mention the earliest version that you know has the issue.
|
||||
* Fork the repository on GitHub
|
||||
|
||||
|
||||
Making Changes
|
||||
==============
|
||||
|
||||
* You must own the copyright to the patch you're submitting, and be in a
|
||||
position to transfer the copyright to Sixty North by agreeing to the either
|
||||
the `Individual Contributor License Agreement <https://github.com/sixty-north/segpy/raw/master/docs/source/legal/segpy-individual-cla.pdf>`_
|
||||
(for private individuals) or the `Entity Contributor License Agreement <https://github.com/sixty-north/segpy/raw/master/docs/source/legal/segpy-entity-cla.pdf>`_
|
||||
(for corporations or other organisations).
|
||||
* Make small commits in logical units.
|
||||
* Ensure your code is in the spirit of `PEP 8 <https://www.python.org/dev/peps/pep-0008/>`_,
|
||||
although we accept that much of what is in PEP 8 are guidelines
|
||||
rather than rules, so we value readability over strict compliance.
|
||||
* Check for unnecessary whitespace with ``git diff --check`` before committing.
|
||||
* Make sure your commit messages are in the proper format::
|
||||
|
||||
|
||||
Issue #1234 - Make the example in CONTRIBUTING imperative and concrete
|
||||
|
||||
Without this patch applied the example commit message in the CONTRIBUTING
|
||||
document is not a concrete example. This is a problem because the
|
||||
contributor is left to imagine what the commit message should look like
|
||||
based on a description rather than an example. This patch fixes the
|
||||
problem by making the example concrete and imperative.
|
||||
|
||||
The first line is a real life imperative statement with an issue number
|
||||
from our issue tracker. The body describes the behavior without the patch,
|
||||
why this is a problem, and how the patch fixes the problem when applied.
|
||||
|
||||
|
||||
* Make sure you have added the necessary tests for your changes.
|
||||
* Run **all** the tests to assure nothing else was accidentally broken.
|
||||
|
||||
Making Trivial Changes
|
||||
======================
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
For changes of a trivial nature to comments and documentation, it is not
|
||||
always necessary to create a new issue. In this case, it is appropriate
|
||||
to start the first line of a commit with 'Doc -' instead of an issue
|
||||
number::
|
||||
|
||||
Doc - Add documentation commit example to CONTRIBUTING
|
||||
|
||||
There is no example for contributing a documentation commit
|
||||
to the Segpy repository. This is a problem because the contributor
|
||||
is left to assume how a commit of this nature may appear.
|
||||
|
||||
The first line is a real life imperative statement with 'Doc -' in
|
||||
place of what would have been the ticket number in a
|
||||
non-documentation related commit. The body describes the nature of
|
||||
the new documentation or comments added.
|
||||
|
||||
Submitting Changes
|
||||
==================
|
||||
|
||||
* Agree to the `Individual Contributor License Agreement <https://github.com/sixty-north/segpy/raw/master/docs/source/legal/segpy-individual-cla.pdf>`_
|
||||
or the `Entity Contributor License Agreement <https://github.com/sixty-north/segpy/raw/master/docs/source/legal/segpy-entity-cla.pdf>`_
|
||||
by attaching a copy of the current CLA to an email (so we know which
|
||||
version you're agreeing to). The body of the message should contain
|
||||
the text "I, <your name>, [representing <your company>] have read the
|
||||
attached CLA and agree to its terms." Send the email to rob@sixty-north.com
|
||||
* Push your changes to a topic branch in your fork of the repository.
|
||||
* Submit a pull request to the repository in the sixty-north organization.
|
||||
|
||||
|
||||
Additional Resources
|
||||
====================
|
||||
|
||||
* `Individual Contributor License Agreement <https://github.com/sixty-north/segpy/raw/master/docs/source/legal/segpy-individual-cla.pdf>`_
|
||||
* `Entity Contributor License Agreement <https://github.com/sixty-north/segpy/raw/master/docs/source/legal/segpy-entity-cla.pdf>`_
|
||||
* `PEP 8 <https://www.python.org/dev/peps/pep-0008/>`_
|
||||
* `General GitHub documentation <http://help.github.com/>`_
|
||||
* `GitHub pull request documentation <http://help.github.com/send-pull-requests/>`_
|
||||
@@ -0,0 +1,32 @@
|
||||
The SEG Y file format is one of several standards developed by the Society of Exploration Geophysicists for storing
|
||||
geophysical seismic data. It is an open standard, and is controlled by the SEG Technical Standards Committee, a
|
||||
non-profit organization.
|
||||
|
||||
This project aims to implement an open SEG Y module in Python for transporting seismic data between SEG Y files and
|
||||
Python data structures in pure Python.
|
||||
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
*Segpy 2* is currently in alpha, so expect rough edges. That said, it seems to broadly work and is largely feature
|
||||
complete.
|
||||
|
||||
|
||||
What It Does
|
||||
============
|
||||
|
||||
How To Get It
|
||||
=============
|
||||
|
||||
*Segpy* is available on the Python Package index and can be installed with ``pip``::
|
||||
|
||||
$ pip install segpy
|
||||
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
*Segpy 2* work with Python 3.3 and higher. For the majority of use *Segpy 2* has no external
|
||||
dependencies. Optional modules with further dependencies such as *Numpy* are included in the ``segpy.ext`` package of
|
||||
extras.
|
||||
@@ -0,0 +1,665 @@
|
||||
Segpy 2.0 - A Python package for handling SEG Y seismic data
|
||||
Copyright (C) 2015 Sixty North AS
|
||||
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero 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
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
=======
|
||||
Segpy 2
|
||||
=======
|
||||
|
||||
Segpy is open source software created by Sixty North and licensed under the GNU Affero General Public License.
|
||||
|
||||
Alternative commercial license terms are available from Sixty North AS if you wish to redistribute Segpy as
|
||||
part of a proprietary closed source product or deliver software software-as-a-service (SaaS) using Segpy as part
|
||||
of a proprietary closed source service.
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
Build status:
|
||||
|
||||
.. image:: https://travis-ci.org/sixty-north/segpy.svg?branch=master
|
||||
:target: https://travis-ci.org/sixty-north/segpy
|
||||
|
||||
Segpy is alpha software but is usable in its current form. That said you should expect minor breaking API changes
|
||||
between now and official release of a Segpy 2.
|
||||
|
||||
What is Segpy?
|
||||
==============
|
||||
|
||||
The SEG Y file format is one of several standards developed by the Society of Exploration Geophysicists for storing
|
||||
geophysical seismic data. It is an open standard, and is controlled by the SEG Technical Standards Committee, a
|
||||
non-profit organization.
|
||||
|
||||
This project aims to implement an open SEG Y module in Python for transporting seismic data between SEG Y files and
|
||||
Python data structures in pure Python.
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
The easiest way to contribute is to use Segpy submit reports for defects or any other issues you come across. Please
|
||||
see `CONTRIBUTING.rst <https://github.com/sixty-north/segpy/blob/master/CONTRIBUTING.rst>`_ for more details.
|
||||
|
||||
|
||||
Segpy Versions
|
||||
==============
|
||||
|
||||
Segpy 2.0 is a complete re-imagining of a SEG Y reader in Python and represents a complete break from any and all older
|
||||
versions of Segpy. No attempt has been made to maintain API compatibility with earlier versions of Segpy and no code is
|
||||
shared across versions. Although earlier versions of Segpy were open source, they were never 'released' as such.
|
||||
Earlier versions of Segpy are deprecated and completely unsupported.
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Segpy.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Segpy.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Segpy"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Segpy"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
@@ -0,0 +1,2 @@
|
||||
sphinx
|
||||
sphinx_rtd_theme
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% source
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Segpy.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Segpy.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Segpy documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sat Jan 31 17:56:17 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
|
||||
# on_rtd is whether we are on readthedocs.org
|
||||
import os
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.ifconfig',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Segpy'
|
||||
copyright = '2015, Robert Smallshire'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '2.0.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '2.0.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
|
||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Segpydoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'Segpy.tex', 'Segpy Documentation',
|
||||
'Robert Smallshire', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'segpy', 'Segpy Documentation',
|
||||
['Robert Smallshire'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Segpy', 'Segpy Documentation',
|
||||
'Robert Smallshire', 'Segpy', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
@@ -0,0 +1,49 @@
|
||||
.. Segpy documentation master file, created by
|
||||
sphinx-quickstart on Sat Jan 31 17:56:17 2015.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
=====
|
||||
Segpy
|
||||
=====
|
||||
|
||||
*Segpy* is a Python package for reading and writing SEG Y data. The SEG Y file format is one of several standards
|
||||
developed by the Society of Exploration Geophysicists for storing geophysical seismic data. It is an open standard, and
|
||||
is controlled by the SEG Technical Standards Committee, a non-profit organization.
|
||||
|
||||
This project aims to implement an open SEG Y module in Python for transporting seismic data between SEG Y files and
|
||||
Python data structures.
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
Front Matter
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
Narrative Documentation
|
||||
-----------------------
|
||||
|
||||
Read this to learn how to use *Segpy*:
|
||||
|
||||
|
||||
Reference Documentation
|
||||
-----------------------
|
||||
|
||||
Descriptions and examples for every public function, class and method in *Segpy*.
|
||||
|
||||
|
||||
Change History
|
||||
--------------
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from segpy.reader import create_reader
|
||||
from segpy.trace_header import TraceHeaderRev1
|
||||
from segpy.types import Int16
|
||||
from segpy.writer import write_segy
|
||||
from segpy.header import are_equal, field
|
||||
|
||||
|
||||
class CustomTraceHeader(TraceHeaderRev1):
|
||||
|
||||
unassigned_1 = field(
|
||||
Int16, offset=233, default=0, documentation="Unassigned 1")
|
||||
|
||||
unassigned_2 = field(
|
||||
Int16, offset=235, default=0, documentation="Unassigned 2")
|
||||
|
||||
unassigned_3 = field(
|
||||
Int16, offset=237, default=0, documentation="Unassigned 3")
|
||||
|
||||
unassigned_4 = field(
|
||||
Int16, offset=239, default=0, documentation="Unassigned 4")
|
||||
|
||||
|
||||
in_filename = "data/rth.segy"
|
||||
out_filename = "data/rth_out2.segy"
|
||||
|
||||
in_file = open(in_filename, 'rb')
|
||||
|
||||
with open(out_filename, 'wb') as out_file:
|
||||
segy_reader_in = create_reader(in_file, trace_header_format=CustomTraceHeader)
|
||||
write_segy(out_file, segy_reader_in, trace_header_format=CustomTraceHeader)
|
||||
|
||||
out_file = open(out_filename, 'rb')
|
||||
segy_reader_out = create_reader(in_file, trace_header_format=CustomTraceHeader)
|
||||
|
||||
for trace_index in segy_reader_in.trace_indexes():
|
||||
trace_offset = segy_reader_in._trace_offset_catalog[trace_index]
|
||||
print(trace_index, hex(trace_offset))
|
||||
head0 = segy_reader_in.trace_header(trace_index)
|
||||
head1 = segy_reader_out.trace_header(trace_index)
|
||||
assert are_equal(head0, head1), "Error {}".format(trace_index)
|
||||
|
||||
data0 = segy_reader_in.trace_samples(trace_index)
|
||||
data1 = segy_reader_out.trace_samples(trace_index)
|
||||
assert data0==data1
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""A simple example which loads a SEG Y file and saves it again.
|
||||
|
||||
Usage:
|
||||
|
||||
loadsave.py <in.segy> <out.segy>
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from segpy.reader import create_reader
|
||||
from segpy.writer import write_segy
|
||||
|
||||
|
||||
def load_save(in_filename, out_filename):
|
||||
with open(in_filename, 'rb') as in_file, \
|
||||
open(out_filename, 'wb') as out_file:
|
||||
|
||||
segy_reader = create_reader(in_file)
|
||||
write_segy(out_file, segy_reader)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
try:
|
||||
in_filename = argv[0]
|
||||
out_filename = argv[1]
|
||||
except IndexError:
|
||||
print(globals()['__doc__'], file=sys.stderr)
|
||||
return os.EX_USAGE
|
||||
|
||||
try:
|
||||
load_save(in_filename, out_filename)
|
||||
except (FileNotFoundError, IsADirectoryError) as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOINPUT
|
||||
except PermissionError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOPERM
|
||||
except Exception as e:
|
||||
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
|
||||
return os.EX_SOFTWARE
|
||||
return os.EX_OK
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Displays a simple report of a SEG Y file.
|
||||
|
||||
Usage:
|
||||
|
||||
report.py <in.segy>
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from segpy.reader import create_reader
|
||||
|
||||
|
||||
def report_segy(in_filename):
|
||||
with open(in_filename, 'rb') as in_file:
|
||||
|
||||
segy_reader = create_reader(in_file)
|
||||
print()
|
||||
print("Filename: ", segy_reader.filename)
|
||||
print("SEG Y revision: ", segy_reader.revision)
|
||||
print("Number of traces: ", segy_reader.num_traces())
|
||||
print("Data format: ",
|
||||
segy_reader.data_sample_format_description)
|
||||
print("Dimensionality: ", segy_reader.dimensionality)
|
||||
|
||||
try:
|
||||
print("Number of CDPs: ", segy_reader.num_cdps())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
print("Number of inlines: ", segy_reader.num_inlines())
|
||||
print("Number of crosslines: ", segy_reader.num_xlines())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
print("=== BEGIN TEXTUAL REEL HEADER ===")
|
||||
for line in segy_reader.textual_reel_header:
|
||||
print(line[3:])
|
||||
print("=== END TEXTUAL REEL HEADER ===")
|
||||
print()
|
||||
print("=== BEGIN EXTENDED TEXTUAL HEADER ===")
|
||||
print(segy_reader.extended_textual_header)
|
||||
print("=== END EXTENDED TEXTUAL_HEADER ===")
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
try:
|
||||
in_filename = argv[0]
|
||||
except IndexError:
|
||||
print(globals()['__doc__'], file=sys.stderr)
|
||||
return os.EX_USAGE
|
||||
|
||||
try:
|
||||
report_segy(in_filename)
|
||||
except (FileNotFoundError, IsADirectoryError) as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOINPUT
|
||||
except PermissionError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOPERM
|
||||
except Exception as e:
|
||||
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
|
||||
return os.EX_SOFTWARE
|
||||
return os.EX_OK
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""A simple example which times reading of all traces in a SEG Y file.
|
||||
|
||||
Usage:
|
||||
|
||||
timed_reader.py <in.segy>
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import datetime
|
||||
|
||||
import os
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from segpy.reader import create_reader
|
||||
|
||||
|
||||
def read_traces(in_filename):
|
||||
with open(in_filename, 'rb') as in_file:
|
||||
|
||||
t0 = datetime.datetime.now()
|
||||
|
||||
segy_reader = create_reader(in_file)
|
||||
|
||||
t1 = datetime.datetime.now()
|
||||
|
||||
for trace_index in segy_reader.trace_indexes():
|
||||
trace = segy_reader.trace_samples(trace_index)
|
||||
|
||||
t2 = datetime.datetime.now()
|
||||
|
||||
time_to_read_header = (t1 - t0).total_seconds()
|
||||
time_to_read_traces = (t2 - t1).total_seconds()
|
||||
time_to_read_both = (t2 - t0).total_seconds()
|
||||
|
||||
print("Time to read headers : {} seconds", time_to_read_header)
|
||||
print("Time to read traces : {} seconds", time_to_read_traces)
|
||||
print("Total time : {} seconds", time_to_read_both)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
try:
|
||||
in_filename = argv[0]
|
||||
except IndexError:
|
||||
print(globals()['__doc__'], file=sys.stderr)
|
||||
return os.EX_USAGE
|
||||
|
||||
try:
|
||||
read_traces(in_filename)
|
||||
except (FileNotFoundError, IsADirectoryError) as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOINPUT
|
||||
except PermissionError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOPERM
|
||||
except Exception as e:
|
||||
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
|
||||
return os.EX_SOFTWARE
|
||||
return os.EX_OK
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Extract a timeslice from a 3D seismic volume to a Numpy array.
|
||||
|
||||
This utility assumes the inline and crossline numbers are evenly spaced.
|
||||
Each inline of the source data will be represented as a single row, and
|
||||
each crossline as a single column in the resulting 2D array.
|
||||
|
||||
Usage: timeslice.py [-h] [--dtype DTYPE] [--null NULL]
|
||||
segy-file npy-file slice-index
|
||||
|
||||
Positional arguments:
|
||||
segy-file Path to an existing SEG Y file of 3D seismic data
|
||||
npy-file Path to the Numpy array file to be created for the timeslice
|
||||
slice-index Zero based index of the time slice to be extracted
|
||||
|
||||
Optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--dtype DTYPE Numpy data type. If not provided a dtype compatible with the
|
||||
SEG Y data will be used.
|
||||
--null NULL Sample value to use for missing or short traces.
|
||||
|
||||
Example:
|
||||
|
||||
timeslice.py stack_final_int8.sgy slice_800.npy 800 --null=42.0 --dtype=f
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import numpy as np
|
||||
|
||||
from segpy.reader import create_reader
|
||||
from segpy_numpy.dtypes import make_dtype
|
||||
|
||||
|
||||
class DimensionalityError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def extract_timeslice(segy_filename, out_filename, slice_index, dtype=None, null=0):
|
||||
"""Extract a timeslice from a 3D SEG Y file to a Numpy NPY file.
|
||||
|
||||
Args:
|
||||
segy_filename: Filename of a SEG Y file.
|
||||
|
||||
out_filename: Filename of the NPY file.
|
||||
|
||||
slice_index: The zero-based index (increasing with depth) of the slice to be extracted.
|
||||
|
||||
dtype: Optional Numpy dtype for the result array. If not provided a dtype compatible with
|
||||
the SEG Y data will be used.
|
||||
|
||||
null: Optional sample value to use for missing or short traces. Defaults to zero.
|
||||
"""
|
||||
with open(segy_filename, 'rb') as segy_file:
|
||||
|
||||
segy_reader = create_reader(segy_file)
|
||||
|
||||
if dtype is None:
|
||||
dtype = make_dtype(segy_reader.data_sample_format)
|
||||
|
||||
if segy_reader.dimensionality != 3:
|
||||
raise DimensionalityError("Cannot slice {n} dimensional seismic.".format(segy_reader.dimensionality))
|
||||
|
||||
i_size = segy_reader.num_inlines()
|
||||
x_size = segy_reader.num_xlines()
|
||||
t_size = segy_reader.max_num_trace_samples()
|
||||
|
||||
if not (0 <= slice_index < t_size):
|
||||
raise ValueError("Time slice index {0} out of range {} to {}".format(slice_index, 0, t_size))
|
||||
|
||||
timeslice = np.full((i_size, x_size), null, dtype)
|
||||
|
||||
for inline_num, xline_num in segy_reader.inline_xline_numbers():
|
||||
trace_index = segy_reader.trace_index((inline_num, xline_num))
|
||||
trace = segy_reader.trace_samples(trace_index)
|
||||
|
||||
try:
|
||||
sample = trace[slice_index]
|
||||
except IndexError:
|
||||
sample = null
|
||||
|
||||
i_index = segy_reader.inline_numbers().index(inline_num)
|
||||
x_index = segy_reader.xline_numbers().index(xline_num)
|
||||
|
||||
timeslice[i_index, x_index] = sample
|
||||
|
||||
np.save(out_filename, timeslice)
|
||||
|
||||
|
||||
def nullable_dtype(s):
|
||||
return None if s == "" else np.dtype(s)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("segy_file", metavar="segy-file",
|
||||
help="Path to an existing SEG Y file of 3D seismic data")
|
||||
|
||||
parser.add_argument("npy_file", metavar="npy-file",
|
||||
help="Path to the Numpy array file to be created for the timeslice")
|
||||
|
||||
parser.add_argument("slice_index", metavar="slice-index", type=int,
|
||||
help="Zero based index of the time slice to be extracted", )
|
||||
|
||||
parser.add_argument("--dtype", type=nullable_dtype, default="",
|
||||
help="Numpy data type. If not provided a dtype compatible with the SEG Y data will be used.")
|
||||
|
||||
parser.add_argument("--null", type=float, default=0.0,
|
||||
help="Sample value to use for missing or short traces.")
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
extract_timeslice(args.segy_file,
|
||||
args.npy_file,
|
||||
args.slice_index,
|
||||
args.dtype,
|
||||
args.null)
|
||||
except (FileNotFoundError, IsADirectoryError) as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOINPUT
|
||||
except PermissionError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOPERM
|
||||
except Exception as e:
|
||||
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
|
||||
return os.EX_SOFTWARE
|
||||
return os.EX_OK
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
Segpy-Numpy offers tools for working with both *Segpy* and *Numpy*. See the *Segpy* documentation for further details.
|
||||
|
||||
|
||||
What It Does
|
||||
============
|
||||
|
||||
How To Get It
|
||||
=============
|
||||
|
||||
*Segpy-Numpy* is available on the Python Package index and can be installed with ``pip``::
|
||||
|
||||
$ pip install segpy-numpy
|
||||
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
*Segpy-Numpy* should work with Python 3.2 and higher (and 2.7 for now). For the majority of use *Segpy 2* has no
|
||||
externaldependencies. Optional modules with further dependencies such as *Numpy* are included in the ``segpy.ext``
|
||||
package of extras.
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Extract a single field from the trace headers of 3D seismic volume to a 2D Numpy array.
|
||||
|
||||
This utility assumes the inline and crossline numbers are evenly spaced.
|
||||
Each inline of the source data will be represented as a single row, and
|
||||
each crossline as a single column in the resulting 2D array.
|
||||
|
||||
Usage: extract_trace_header_field.py [-h] [--null NULL]
|
||||
segy-file npy-file field-name
|
||||
|
||||
Positional arguments:
|
||||
segy-file Path to an existing SEG Y file of 3D seismic data
|
||||
npy-file Path to the Numpy array file to be created for the timeslice
|
||||
field-name Zero based index of the time slice to be extracted
|
||||
|
||||
Optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--dtype DTYPE Numpy data type. If not provided a dtype compatible with the
|
||||
SEG Y data will be used.
|
||||
--null NULL Sample value to use for missing or short traces.
|
||||
|
||||
Example:
|
||||
|
||||
extract_trace_header_field.py stack_final_int8.sgy slice_800.npy ensemble_num
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numpy import s_
|
||||
|
||||
from segpy.reader import create_reader
|
||||
from segpy.trace_header import TraceHeaderRev1
|
||||
from segpy_numpy.dtypes import make_dtype
|
||||
from segpy_numpy.extract import extract_inline_3d, extract_trace_header_field_3d
|
||||
|
||||
|
||||
class DimensionalityError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def extract_header_field(segy_filename, out_filename, field_name, null=None):
|
||||
"""Extract a timeslice from a 3D SEG Y file to a Numpy NPY file.
|
||||
|
||||
Args:
|
||||
segy_filename: Filename of a SEG Y file.
|
||||
|
||||
out_filename: Filename of the NPY file.
|
||||
|
||||
inline_index: The zero-based index (increasing with depth) of the slice to be extracted.
|
||||
|
||||
null: Optional sample value to use for missing or short traces. Defaults to zero.
|
||||
"""
|
||||
header_field = getattr(TraceHeaderRev1, field_name)
|
||||
|
||||
with open(segy_filename, 'rb') as segy_file:
|
||||
|
||||
segy_reader = create_reader(segy_file)
|
||||
|
||||
header_field_arrays = extract_trace_header_field_3d(segy_reader, [header_field], null)
|
||||
return header_field_arrays
|
||||
|
||||
|
||||
def nullable_int(s):
|
||||
return None if not bool(s) else int(s)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("segy_file", metavar="segy-file",
|
||||
help="Path to an existing SEG Y file of 3D seismic data")
|
||||
|
||||
parser.add_argument("npy_file", metavar="npy-file",
|
||||
help="Path to the Numpy array file to be created for the timeslice")
|
||||
|
||||
parser.add_argument("field_name", metavar="field-name", type=str,
|
||||
help="Name of the trace header field to be extracted", )
|
||||
|
||||
parser.add_argument("--null", type=nullable_int, default="",
|
||||
help="Header value to use for missing or short traces.")
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
extract_header_field(
|
||||
segy_filename=args.segy_file,
|
||||
out_filename=args.npy_file,
|
||||
field_name=args.field_name,
|
||||
null=args.null)
|
||||
except (FileNotFoundError, IsADirectoryError) as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOINPUT
|
||||
except PermissionError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOPERM
|
||||
except Exception as e:
|
||||
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
|
||||
return os.EX_SOFTWARE
|
||||
return os.EX_OK
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Extract an inline from a 3D seismic volume to a Numpy array.
|
||||
|
||||
This utility assumes the inline and crossline numbers are evenly spaced.
|
||||
Each inline of the source data will be represented as a single row, and
|
||||
each crossline as a single column in the resulting 2D array.
|
||||
|
||||
Usage: inline.py [-h] [--dtype DTYPE] [--null NULL]
|
||||
segy-file npy-file inline-number
|
||||
|
||||
Positional arguments:
|
||||
segy-file Path to an existing SEG Y file of 3D seismic data
|
||||
npy-file Path to the Numpy array file to be created for the timeslice
|
||||
slice-index Zero based index of the time slice to be extracted
|
||||
|
||||
Optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--dtype DTYPE Numpy data type. If not provided a dtype compatible with the
|
||||
SEG Y data will be used.
|
||||
--null NULL Sample value to use for missing or short traces.
|
||||
|
||||
Example:
|
||||
|
||||
inline.py stack_final_int8.sgy slice_800.npy 800 --null=42.0 --dtype=f
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numpy import s_
|
||||
|
||||
from segpy.reader import create_reader
|
||||
from segpy_numpy.dtypes import make_dtype
|
||||
from segpy_numpy.extract import extract_inline_3d
|
||||
|
||||
|
||||
class DimensionalityError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def extract_inline(segy_filename, out_filename, inline_number, null=None):
|
||||
"""Extract a timeslice from a 3D SEG Y file to a Numpy NPY file.
|
||||
|
||||
Args:
|
||||
segy_filename: Filename of a SEG Y file.
|
||||
|
||||
out_filename: Filename of the NPY file.
|
||||
|
||||
inline_index: The zero-based index (increasing with depth) of the slice to be extracted.
|
||||
|
||||
null: Optional sample value to use for missing or short traces. Defaults to zero.
|
||||
"""
|
||||
with open(segy_filename, 'rb') as segy_file:
|
||||
|
||||
segy_reader = create_reader(segy_file)
|
||||
inline_array = extract_inline_3d(segy_reader, inline_number, null=null)
|
||||
return inline_array
|
||||
|
||||
|
||||
def nullable_float(s):
|
||||
return None if not bool(s) else float(s)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("segy_file", metavar="segy-file",
|
||||
help="Path to an existing SEG Y file of 3D seismic data")
|
||||
|
||||
parser.add_argument("npy_file", metavar="npy-file",
|
||||
help="Path to the Numpy array file to be created for the timeslice")
|
||||
|
||||
parser.add_argument("inline_number", metavar="inline-number", type=int,
|
||||
help="Zero based index of the inline to be extracted", )
|
||||
|
||||
parser.add_argument("--null", type=nullable_float, default="",
|
||||
help="Sample value to use for missing or short traces.")
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
extract_inline(
|
||||
segy_filename=args.segy_file,
|
||||
out_filename=args.npy_file,
|
||||
inline_number=args.inline_number,
|
||||
null=args.null)
|
||||
except (FileNotFoundError, IsADirectoryError) as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOINPUT
|
||||
except PermissionError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return os.EX_NOPERM
|
||||
except Exception as e:
|
||||
traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr)
|
||||
return os.EX_SOFTWARE
|
||||
return os.EX_OK
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Optional interoperability with Numpy."""
|
||||
|
||||
import numpy
|
||||
|
||||
NUMPY_DTYPES = {'ibm': numpy.dtype('f4'),
|
||||
'int32': numpy.dtype('i4'),
|
||||
'int16': numpy.dtype('i2'),
|
||||
'float32': numpy.dtype('f4'),
|
||||
'int8': numpy.dtype('i1')}
|
||||
|
||||
|
||||
def make_dtype(data_sample_format): # TODO: What is the correct name for this arg?
|
||||
"""Convert a SEG Y data sample format to a compatible numpy dtype.
|
||||
|
||||
Note :
|
||||
IBM float data sample formats ('ibm') will correspond to IEEE float data types.
|
||||
|
||||
Args:
|
||||
data_sample_format: A data sample format string.
|
||||
|
||||
Returns:
|
||||
A numpy.dtype instance.
|
||||
|
||||
Raises:
|
||||
ValueError: For unrecognised data sample format strings.
|
||||
"""
|
||||
try:
|
||||
return NUMPY_DTYPES[data_sample_format]
|
||||
except KeyError:
|
||||
raise ValueError("Unknown data sample format string {!r}".format(data_sample_format))
|
||||
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
"""Tools for interoperability between Segpy and Numpy arrays."""
|
||||
from collections import namedtuple
|
||||
import numpy as np
|
||||
from segpy.header import Header, SubFormatMeta
|
||||
from segpy.packer import make_header_packer
|
||||
|
||||
from segpy.util import ensure_superset
|
||||
from segpy_numpy.dtypes import make_dtype
|
||||
|
||||
|
||||
def extract_trace_header_field_3d(reader_3d, fields, inline_numbers=None, xline_numbers=None, null=None):
|
||||
"""Extract a single trace header field from all trace headers as an array.
|
||||
|
||||
Args:
|
||||
reader_3d: A SegYReader3D
|
||||
|
||||
fields: A an iterable series where each item is either the name of a field as a string
|
||||
or an object such as a NamedField with a 'name' attribute which in turn is the name
|
||||
of a field as a string, such as a NamedField.
|
||||
|
||||
inline_numbers: The inline numbers for which traces are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All traces within the each crossline will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those traces at
|
||||
inline numbers corresponding to the items in the sequence will be extracted. The
|
||||
traces will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example inline_numbers=range(100, 200, 2) will extract alternate
|
||||
traces from inline number 100 to inline number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
inline numbers. For example inline_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred traces, irrespective of their numbers.
|
||||
|
||||
xline_numbers: The crossline numbers at which traces are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All traces at within each inline will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those traces at
|
||||
crossline numbers corresponding to the items in the sequence will be extracted. The
|
||||
traces will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example xline_numbers=range(100, 200, 2) will extract alternate
|
||||
traces from crossline number 100 to crossline number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
crossline numbers. For example xline_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred traces, irrespective of their numbers.
|
||||
|
||||
null: An optional null value for missing traces. The null value must be convertible
|
||||
to all field value types.
|
||||
|
||||
Returns:
|
||||
A namedtuple object with attributes which are two-dimensional Numpy arrays.
|
||||
If a null value was specified the arrays will be ndarrays, otherwise they
|
||||
will be masked arrays. The attributes of the named tuple are in the same
|
||||
order as the fields specified in the `fields` argument.
|
||||
|
||||
Raises:
|
||||
AttributeError: If the the named fields do not exist in the trace header definition.
|
||||
"""
|
||||
field_names = [_extract_field_name(field) for field in fields]
|
||||
|
||||
inline_numbers = ensure_superset(reader_3d.inline_numbers(), inline_numbers)
|
||||
xline_numbers = ensure_superset(reader_3d.xline_numbers(), xline_numbers)
|
||||
shape = (len(inline_numbers), len(xline_numbers))
|
||||
|
||||
class SubFormat(metaclass=SubFormatMeta,
|
||||
parent_format=reader_3d.trace_header_format_class,
|
||||
parent_field_names=field_names):
|
||||
pass
|
||||
|
||||
sub_header_packer = make_header_packer(SubFormat, reader_3d.endian)
|
||||
TraceHeaderArrays = namedtuple('TraceHeaderArrays', field_names)
|
||||
|
||||
arrays = (_make_array(shape,
|
||||
make_dtype(getattr(SubFormat, field_name).value_type.SEG_Y_TYPE),
|
||||
null)
|
||||
for field_name in field_names)
|
||||
|
||||
trace_header_arrays = TraceHeaderArrays(*arrays)
|
||||
|
||||
for inline_index, inline_number in enumerate(inline_numbers):
|
||||
for xline_index, xline_number in enumerate(xline_numbers):
|
||||
inline_xline_number = (inline_number, xline_number)
|
||||
if reader_3d.has_trace_index(inline_xline_number):
|
||||
trace_index = reader_3d.trace_index((inline_number, xline_number))
|
||||
trace_header = reader_3d.trace_header(trace_index, sub_header_packer)
|
||||
|
||||
for field_name, a in zip(field_names, trace_header_arrays):
|
||||
field_value = getattr(trace_header, field_name)
|
||||
a[inline_index, xline_index] = field_value
|
||||
|
||||
return trace_header_arrays
|
||||
|
||||
|
||||
def extract_trace(reader, trace_index, sample_numbers):
|
||||
"""Extract an single trace as a one-dimensional array.
|
||||
|
||||
Args:
|
||||
reader: A SegYReader3D object.
|
||||
|
||||
trace_index: The index of the trace to be extracted.
|
||||
|
||||
sample_numbers: The sample numbers within each trace at which samples are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All samples within the trace will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those samples at
|
||||
sample numbers corresponding to the items in the sequence will be extracted. The
|
||||
samples will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example sample_numbers=range(100, 200, 2) will extract alternate
|
||||
samples from sample number 100 to sample number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
sample numbers. For example sample_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred samples, irrespective of their numbers.
|
||||
|
||||
Returns:
|
||||
A one-dimensional array.
|
||||
"""
|
||||
if not reader.has_trace_index(trace_index):
|
||||
raise ValueError("Inline number {} not present in {}".format(trace_index, reader))
|
||||
|
||||
sample_numbers = ensure_superset(range(0, reader.max_num_trace_samples()), sample_numbers)
|
||||
|
||||
trace_sample_start = sample_numbers[0]
|
||||
trace_sample_stop = min(sample_numbers[-1] + 1, reader.num_trace_samples(trace_index))
|
||||
trace_samples = reader.trace_samples(trace_index, trace_sample_start, trace_sample_stop)
|
||||
arr = np.fromiter((trace_samples[sample_number - trace_sample_start] for sample_number in sample_numbers),
|
||||
make_dtype(reader.data_sample_format))
|
||||
return arr
|
||||
|
||||
|
||||
def extract_inline_3d(reader_3d, inline_number, xline_numbers=None, sample_numbers=None, null=None):
|
||||
"""Extract an inline as a two-dimensional array.
|
||||
|
||||
Args:
|
||||
reader_3d: A SegYReader3D object.
|
||||
|
||||
inline_number: The number of the inline to be extracted.
|
||||
|
||||
xline_numbers: The crossline numbers within the inline at which traces are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All traces within the inline will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those traces at
|
||||
crossline numbers corresponding to the items in the sequence will be extracted. The
|
||||
traces will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example xline_numbers=range(100, 200, 2) will extract alternate
|
||||
traces from crossline number 100 to crossline number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
crossline numbers. For example xline_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred traces, irrespective of their numbers.
|
||||
|
||||
sample_numbers: The sample numbers within each trace at which samples are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All samples within the trace will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those samples at
|
||||
sample numbers corresponding to the items in the sequence will be extracted. The
|
||||
samples will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example sample_numbers=range(100, 200, 2) will extract alternate
|
||||
samples from sample number 100 to sample number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
sample numbers. For example sample_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred samples, irrespective of their numbers.
|
||||
|
||||
null: A null value. When None is specified as the null value a masked array will be returned.
|
||||
|
||||
Returns:
|
||||
A two-dimensional array. If null is None a masked array will be returned, otherwise
|
||||
a regular array will be returned. The first (slowest changing) index will correspond
|
||||
to the traces (index zero will correspond to the first crossline number). The
|
||||
second (fastest changing) index will correspond to the samples (index zero will
|
||||
correspond to the first sample number).
|
||||
"""
|
||||
if inline_number not in reader_3d.inline_numbers():
|
||||
raise ValueError("Inline number {} not present in {}".format(inline_number, reader_3d))
|
||||
|
||||
xline_numbers = ensure_superset(reader_3d.xline_numbers(), xline_numbers)
|
||||
sample_numbers = ensure_superset(range(0, reader_3d.max_num_trace_samples()), sample_numbers)
|
||||
shape = (len(xline_numbers), len(sample_numbers))
|
||||
dtype = make_dtype(reader_3d.data_sample_format)
|
||||
array = _make_array(shape, dtype, null)
|
||||
|
||||
if isinstance(sample_numbers, range):
|
||||
_populate_inline_array_over_sample_range(reader_3d, inline_number, xline_numbers, sample_numbers, array)
|
||||
else:
|
||||
_populate_inline_array_numbered_samples(reader_3d, inline_number, xline_numbers, sample_numbers, array)
|
||||
|
||||
return array
|
||||
|
||||
|
||||
def _populate_inline_array_numbered_samples(reader_3d, inline_number, xline_numbers, sample_numbers, array):
|
||||
for xline_index, xline_number in enumerate(xline_numbers):
|
||||
inline_xline_number = (inline_number, xline_number)
|
||||
if reader_3d.has_trace_index(inline_xline_number):
|
||||
trace_index = reader_3d.trace_index(inline_xline_number)
|
||||
num_trace_samples = reader_3d.num_trace_samples(trace_index)
|
||||
trace_sample_start = sample_numbers[0]
|
||||
trace_sample_stop = min(sample_numbers[-1] + 1, num_trace_samples)
|
||||
trace_samples = reader_3d.trace_samples(trace_index, trace_sample_start, trace_sample_stop)
|
||||
for sample_index, sample_number in enumerate(sample_numbers):
|
||||
array[xline_index, sample_index] = trace_samples[sample_number - trace_sample_start]
|
||||
|
||||
|
||||
def _populate_inline_array_over_sample_range(reader_3d, inline_number, xline_numbers, sample_numbers, array):
|
||||
for xline_index, xline_number in enumerate(xline_numbers):
|
||||
inline_xline_number = (inline_number, xline_number)
|
||||
if reader_3d.has_trace_index(inline_xline_number):
|
||||
trace_index = reader_3d.trace_index(inline_xline_number)
|
||||
num_trace_samples = reader_3d.num_trace_samples(trace_index)
|
||||
trace_sample_stop = min(sample_numbers.stop, num_trace_samples)
|
||||
trace_samples = reader_3d.trace_samples(trace_index, sample_numbers.start, trace_sample_stop)
|
||||
source_slice = slice(sample_numbers.start, trace_sample_stop, sample_numbers.step)
|
||||
array[xline_index, :] = trace_samples[source_slice]
|
||||
|
||||
|
||||
def extract_xline_3d(reader_3d, xline_number, inline_numbers=None, sample_numbers=None, null=None):
|
||||
"""Extract an inline as a two-dimensional array.
|
||||
|
||||
Args:
|
||||
reader_3d: A SegYReader3D object.
|
||||
|
||||
xline_number: The number of the xline to be extracted.
|
||||
|
||||
inline_numbers: The inline numbers within the crossline at which traces are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All traces within the crossline will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those traces at
|
||||
inline numbers corresponding to the items in the sequence will be extracted. The
|
||||
traces will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example inline_numbers=range(100, 200, 2) will extract alternate
|
||||
traces from inline number 100 to inline number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
inline numbers. For example inline_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred traces, irrespective of their numbers.
|
||||
|
||||
sample_numbers: The sample numbers within each trace at which samples are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All samples within the trace will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those samples at
|
||||
sample numbers corresponding to the items in the sequence will be extracted. The
|
||||
samples will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example sample_numbers=range(100, 200, 2) will extract alternate
|
||||
samples from sample number 100 to sample number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
sample numbers. For example sample_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred samples, irrespective of their numbers.
|
||||
|
||||
null: A null value. When None is specified as the null value a masked array will be returned.
|
||||
|
||||
Returns:
|
||||
A two-dimensional array. If null is None a masked array will be returned, otherwise
|
||||
a regular array will be returned. The first (slowest changing) index will correspond
|
||||
to the traces (index zero will correspond to the first inline number). The
|
||||
second (fastest changing) index will correspond to the samples (index zero will
|
||||
correspond to the first sample number).
|
||||
"""
|
||||
if xline_number not in reader_3d.xline_numbers():
|
||||
raise ValueError("Crossline number {} not present in {}".format(xline_number, reader_3d))
|
||||
|
||||
inline_numbers = ensure_superset(reader_3d.inline_numbers(), inline_numbers)
|
||||
sample_numbers = ensure_superset(range(0, reader_3d.max_num_trace_samples()), sample_numbers)
|
||||
shape = (len(inline_numbers), len(sample_numbers))
|
||||
dtype = make_dtype(reader_3d.data_sample_format)
|
||||
array = _make_array(shape, dtype, null)
|
||||
|
||||
if isinstance(sample_numbers, range):
|
||||
_populate_xline_array_over_sample_range(reader_3d, xline_number, inline_numbers, sample_numbers, array)
|
||||
else:
|
||||
_populate_xline_array_numbered_samples(reader_3d, xline_number, inline_numbers, sample_numbers, array)
|
||||
|
||||
return array
|
||||
|
||||
|
||||
def _populate_xline_array_numbered_samples(reader_3d, xline_number, inline_numbers, sample_numbers, array):
|
||||
for inline_index, inline_number in enumerate(inline_numbers):
|
||||
inline_xline_number = (inline_number, xline_number)
|
||||
if reader_3d.has_trace_index(inline_xline_number):
|
||||
trace_index = reader_3d.trace_index(inline_xline_number)
|
||||
num_trace_samples = reader_3d.num_trace_samples(trace_index)
|
||||
trace_sample_start = sample_numbers[0]
|
||||
trace_sample_stop = min(sample_numbers[-1] + 1, num_trace_samples)
|
||||
trace_samples = reader_3d.trace_samples(trace_index, trace_sample_start, trace_sample_stop)
|
||||
for sample_index, sample_number in enumerate(sample_numbers):
|
||||
array[inline_index, sample_index] = trace_samples[sample_number - trace_sample_start]
|
||||
|
||||
|
||||
def _populate_xline_array_over_sample_range(reader_3d, xline_number, inline_numbers, sample_numbers, array):
|
||||
for inline_index, inline_number in enumerate(inline_numbers):
|
||||
inline_xline_number = (inline_number, xline_number)
|
||||
if reader_3d.has_trace_index(inline_xline_number):
|
||||
trace_index = reader_3d.trace_index(inline_xline_number)
|
||||
num_trace_samples = reader_3d.num_trace_samples(trace_index)
|
||||
trace_sample_stop = min(sample_numbers.stop, num_trace_samples)
|
||||
trace_samples = reader_3d.trace_samples(trace_index, sample_numbers.start, trace_sample_stop)
|
||||
source_slice = slice(sample_numbers.start, trace_sample_stop, sample_numbers.step)
|
||||
array[inline_index, :] = trace_samples[source_slice]
|
||||
|
||||
|
||||
def extract_timeslice_3d(reader_3d, sample_number, inline_numbers=None, xline_numbers=None, null=None):
|
||||
"""Extract a single timeslice header field from all trace headers as an array.
|
||||
|
||||
Args:
|
||||
reader_3d: A SegYReader3D
|
||||
|
||||
sample_number: The zero-based sample index.
|
||||
|
||||
inline_numbers: The inline numbers for which traces are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All traces within the each crossline will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those traces at
|
||||
inline numbers corresponding to the items in the sequence will be extracted. The
|
||||
traces will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example inline_numbers=range(100, 200, 2) will extract alternate
|
||||
traces from inline number 100 to inline number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
inline numbers. For example inline_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred traces, irrespective of their numbers.
|
||||
|
||||
xline_numbers: The crossline numbers at which traces are to be extracted.
|
||||
This argument can be specified in three ways:
|
||||
|
||||
None (the default) - All traces at within each inline will be be extracted.
|
||||
|
||||
sequence - When a sequence, such as a range or a list is provided only those traces at
|
||||
crossline numbers corresponding to the items in the sequence will be extracted. The
|
||||
traces will always be extracted in increasing numeric order and duplicate entries
|
||||
will be ignored. For example xline_numbers=range(100, 200, 2) will extract alternate
|
||||
traces from crossline number 100 to crossline number 198 inclusive.
|
||||
|
||||
slice - When a slice object is provided the slice will be applied to the sequence of all
|
||||
crossline numbers. For example xline_numbers=slice(100, -100) will omit the first
|
||||
one hundred and the last one hundred traces, irrespective of their numbers.
|
||||
|
||||
null: An optional null value for missing traces. The null value must be convertible
|
||||
to all field value types.
|
||||
|
||||
Returns:
|
||||
An namedtuple object with attributes which are two-dimensional Numpy arrays.
|
||||
If a null value was specified the arrays will be ndarrays, otherwise they
|
||||
will be masked arrays. The attributes of the named tuple are in the same
|
||||
order as the fields specified in the `fields` argument.
|
||||
|
||||
Raises:
|
||||
AttributeError: If the the named fields do not exist in the trace header definition.
|
||||
"""
|
||||
|
||||
inline_numbers = ensure_superset(reader_3d.inline_numbers(), inline_numbers)
|
||||
xline_numbers = ensure_superset(reader_3d.xline_numbers(), xline_numbers)
|
||||
shape = (len(inline_numbers), len(xline_numbers))
|
||||
dtype = make_dtype(reader_3d.data_sample_format)
|
||||
array = _make_array(shape, dtype, null)
|
||||
sample_number_stop = sample_number + 1
|
||||
|
||||
for inline_index, inline_number in enumerate(inline_numbers):
|
||||
for xline_index, xline_number in enumerate(xline_numbers):
|
||||
inline_xline_number = (inline_number, xline_number)
|
||||
if reader_3d.has_trace_index(inline_xline_number):
|
||||
trace_index = reader_3d.trace_index((inline_number, xline_number))
|
||||
trace_samples = reader_3d.trace_samples(trace_index, sample_number, sample_number_stop)
|
||||
array[inline_index, xline_index] = trace_samples[0]
|
||||
return array
|
||||
|
||||
def _make_array(shape, dtype, null=None):
|
||||
"""Make an array"""
|
||||
if null is None:
|
||||
return np.ma.masked_all(shape, dtype)
|
||||
array = np.empty(shape, dtype)
|
||||
array.fill(null)
|
||||
return array
|
||||
|
||||
def _extract_field_name(field):
|
||||
"""Args:
|
||||
field: If field in an object with a name attribute the name is returned. If field is a string it is returned
|
||||
unmodified.
|
||||
Raises:
|
||||
TypeError:
|
||||
"""
|
||||
if isinstance(field, str):
|
||||
return field
|
||||
try:
|
||||
return field.name
|
||||
except AttributeError:
|
||||
raise TypeError("{!r} neither is a string nor has a 'name' attribute".format(field))
|
||||
@@ -0,0 +1,5 @@
|
||||
[bdist_wheel]
|
||||
# This flag says that the code is written to work on both Python 2 and Python
|
||||
# 3. If at all possible, it is good practice to do this. If you cannot, you
|
||||
# will need to generate wheels for each Python version that you support.
|
||||
universal=1
|
||||
@@ -0,0 +1,127 @@
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
from setuptools import setup, find_packages # Always prefer setuptools over distutils
|
||||
from codecs import open # To use a consistent encoding
|
||||
from os import path
|
||||
|
||||
|
||||
def read(*names, **kwargs):
|
||||
with io.open(
|
||||
os.path.join(os.path.dirname(__file__), *names),
|
||||
encoding=kwargs.get("encoding", "utf8")
|
||||
) as fp:
|
||||
return fp.read()
|
||||
|
||||
|
||||
def find_version(*file_paths):
|
||||
version_file = read(*file_paths)
|
||||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
|
||||
version_file, re.M)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
|
||||
def local_file(name):
|
||||
return os.path.join(here, name)
|
||||
|
||||
|
||||
|
||||
# Get the long description from the relevant file
|
||||
with open(local_file('DESCRIPTION.rst'), encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='segpy_numpy',
|
||||
|
||||
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||
# the version across setup.py and the project code, see
|
||||
# https://packaging.python.org/en/latest/single_source_version.html
|
||||
version=find_version("segpy_numpy/__init__.py"),
|
||||
|
||||
description='Interoperability between Numpy with Segpy.',
|
||||
long_description=long_description,
|
||||
|
||||
# The project's main homepage.
|
||||
url='https://github.com/sixty-north/segpy',
|
||||
|
||||
# Author details
|
||||
author='Sixty North AS',
|
||||
author_email='rob@sixty-north.com',
|
||||
|
||||
# Choose your license
|
||||
license='GNU Affero General Public License v3',
|
||||
|
||||
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
classifiers=[
|
||||
# How mature is this project? Common values are
|
||||
# 3 - Alpha
|
||||
# 4 - Beta
|
||||
# 5 - Production/Stable
|
||||
'Development Status :: 3 - Alpha',
|
||||
|
||||
# Indicate who your project is intended for
|
||||
'Intended Audience :: Developers',
|
||||
'Topic :: Scientific/Engineering',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
|
||||
# Pick your license as you wish (should match "license" above)
|
||||
'License :: OSI Approved :: MIT License',
|
||||
|
||||
# Specify the Python versions you support here. In particular, ensure
|
||||
# that you indicate whether you support Python 2, Python 3 or both.
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
],
|
||||
|
||||
# What does your project relate to?
|
||||
keywords='seismic geocomputing geophysics numpy',
|
||||
|
||||
# You can just specify the packages manually here if your project is
|
||||
# simple. Or you can use find_packages().
|
||||
packages=find_packages(here, exclude=['segpy', 'contrib', 'docs', 'test*']),
|
||||
|
||||
# List run-time dependencies here. These will be installed by pip when your
|
||||
# project is installed. For an analysis of "install_requires" vs pip's
|
||||
# requirements files see:
|
||||
# https://packaging.python.org/en/latest/requirements.html
|
||||
install_requires=['numpy'],
|
||||
|
||||
# List additional groups of dependencies here (e.g. development dependencies).
|
||||
# You can install these using the following syntax, for example:
|
||||
# $ pip install -e .[dev,test]
|
||||
extras_require = {
|
||||
'dev': ['check-manifest', 'wheel'],
|
||||
'doc': ['sphinx', 'cartouche'],
|
||||
'test': ['coverage', 'hypothesis'],
|
||||
},
|
||||
|
||||
# If there are data files included in your packages that need to be
|
||||
# installed, specify them here. If using Python 2.6 or less, then these
|
||||
# have to be included in MANIFEST.in as well.
|
||||
package_data={
|
||||
},
|
||||
|
||||
# Although 'package_data' is the preferred approach, in some case you may
|
||||
# need to place data files outside of your packages.
|
||||
# see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files
|
||||
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
|
||||
data_files=[],
|
||||
|
||||
# To provide executable scripts, use entry points in preference to the
|
||||
# "scripts" keyword. Entry points provide cross-platform support and allow
|
||||
# pip to create the appropriate form of executable for the target platform.
|
||||
|
||||
# Declare entry-points to modules.
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import unittest
|
||||
|
||||
|
||||
class MyTestCase(unittest.TestCase):
|
||||
def test_something(self):
|
||||
self.assertEqual(True, False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = '2.0.0a2'
|
||||
@@ -0,0 +1,223 @@
|
||||
from segpy.header import FormatMeta, field
|
||||
from segpy.types import Int32, Int16
|
||||
|
||||
|
||||
class BinaryReelHeader(metaclass=FormatMeta):
|
||||
|
||||
START_OFFSET_IN_BYTES = 3201
|
||||
LENGTH_IN_BYTES = 400
|
||||
|
||||
job_id_num = field(
|
||||
Int32, offset=3201, default=0, documentation=
|
||||
"Job identification number")
|
||||
|
||||
line_num = field(
|
||||
Int32, offset=3205, default=0, documentation=
|
||||
"Line number. For 3-D poststack data, this will typically contain the in-line number."
|
||||
)
|
||||
|
||||
reel_num = field(
|
||||
Int32, offset=3209, default=0, documentation=
|
||||
"Reel number."
|
||||
)
|
||||
|
||||
data_traces_per_ensemble = field(
|
||||
Int16, offset=3213, default=0, documentation=
|
||||
"Number of data traces per ensemble. Mandatory for prestack data."
|
||||
)
|
||||
|
||||
auxiliary_traces_per_ensemble = field(
|
||||
Int16, offset=3215, default=0, documentation=
|
||||
"Number of auxiliary traces per ensemble. Mandatory for prestack data."
|
||||
)
|
||||
|
||||
sample_interval = field(
|
||||
Int16, offset=3217, default=0, documentation=
|
||||
"Sample interval in microseconds (μs). Mandatory for all data types."
|
||||
)
|
||||
|
||||
original_field_sample_interval = field(
|
||||
Int16, offset=3219, default=0, documentation=
|
||||
"Sample interval in microseconds (μs) of original field recording."
|
||||
)
|
||||
|
||||
num_samples = field(
|
||||
Int16, offset=3221, default=0, documentation=
|
||||
"Number of samples per data trace. Mandatory for all types of data. "
|
||||
"Note: The sample interval and number of samples in the Binary File Header should be for the primary set of "
|
||||
"seismic data traces in the file."
|
||||
)
|
||||
|
||||
original_field_num_samples = field(
|
||||
Int16, offset=3223, default=0, documentation=
|
||||
"Number of samples per data trace for original field recording."
|
||||
)
|
||||
|
||||
data_sample_format = field(
|
||||
Int16, offset=3225, default=5, documentation=
|
||||
"Data sample format code. Mandatory for all data. "
|
||||
"1 = 4-byte IBM floating-point, "
|
||||
"2 = 4-byte, two's complement integer, "
|
||||
"3 = 2-byte, two's complement integer, "
|
||||
"4 = 4-byte fixed-point with gain (obsolete), "
|
||||
"5 = 4-byte IEEE floating-point, "
|
||||
"6 = Not currently used, "
|
||||
"7 = Not currently used, "
|
||||
"8 = 1-byte, two's complement integer."
|
||||
)
|
||||
|
||||
ensemble_fold = field(
|
||||
Int16, offset=3227, default=0, documentation=
|
||||
"Ensemble fold. The expected number of data traces per trace ensemble (e.g. the CMP fold). "
|
||||
"Highly recommended for all types of data."
|
||||
)
|
||||
|
||||
trace_sorting = field(
|
||||
Int16, offset=3229, default=0, documentation=
|
||||
"Trace sorting code (i.e. type of ensemble) : "
|
||||
"-1 = Other (should be explained in user Extended Textual File Header stanza, "
|
||||
"0 = Unknown, "
|
||||
"1 = As recorded (no sorting), "
|
||||
"2 = CDP ensemble, "
|
||||
"3 = Single fold continuous profile 4 = Horizontally stacked, "
|
||||
"5 = Common source point, "
|
||||
"6 = Common receiver point, "
|
||||
"7 = Common offset point, "
|
||||
"8 = Common mid-point, "
|
||||
"9 = Common conversion point. "
|
||||
"Highly recommended for all types of data."
|
||||
)
|
||||
|
||||
vertical_sum_code = field(
|
||||
Int16, offset=3231, default=0, documentation=
|
||||
"Vertical sum code: "
|
||||
"1 = no sum, "
|
||||
"2 = two sum, "
|
||||
"..., "
|
||||
"N=M-1 sum (M=2to32,767)."
|
||||
)
|
||||
|
||||
sweep_frequency_at_start = field(
|
||||
Int16, offset=3233, default=0, documentation=
|
||||
"Sweep frequency at start (Hz)."
|
||||
)
|
||||
|
||||
sweep_frequency_at_end = field(
|
||||
Int16, offset=3235, default=0, documentation=
|
||||
"Sweep frequency at end (Hz)."
|
||||
)
|
||||
|
||||
sweep_length = field(
|
||||
Int16, offset=3237, default=0, documentation=
|
||||
"Sweep length in milliseconds."
|
||||
)
|
||||
|
||||
sweep_type = field(
|
||||
Int16, offset=3239, default=0, documentation=
|
||||
"Sweep type: 1 = linear, 2 = parabolic, 3 = exponential 4 = other."
|
||||
)
|
||||
|
||||
sweep_trace_number = field(
|
||||
Int16, offset=3241, default=0, documentation=
|
||||
"Trace number of sweep channel."
|
||||
)
|
||||
|
||||
sweep_trace_taper_length_at_start = field(
|
||||
Int16, offset=3243, default=0, documentation=
|
||||
"Sweep trace taper length at start in milliseconds."
|
||||
)
|
||||
|
||||
sweep_trace_taper_length_at_end = field(
|
||||
Int16, offset=3245, default=0, documentation=
|
||||
"Sweep trace taper length at end in milliseconds."
|
||||
)
|
||||
|
||||
taper_type = field(
|
||||
Int16, offset=3247, default=0, documentation=
|
||||
"Taper type: 1 = linear, 2 = cos2, 3 = other"
|
||||
)
|
||||
|
||||
correlated_data_traces = field(
|
||||
Int16, offset=3249, default=0, documentation=
|
||||
"Correlated data traces: "
|
||||
"1 = no, "
|
||||
"2 = yes"
|
||||
)
|
||||
|
||||
binary_gain_recovered = field(
|
||||
Int16, offset=3251, default=0, documentation=
|
||||
"Binary gain recovered: "
|
||||
"1 = no, "
|
||||
"2 = yes"
|
||||
)
|
||||
|
||||
amplitude_recovery_method = field(
|
||||
Int16, offset=3253, default=0, documentation=
|
||||
"Amplitude recovery method: "
|
||||
"1 = none, "
|
||||
"2 = spherical divergence, "
|
||||
"3 = AGC, "
|
||||
"4 = other"
|
||||
)
|
||||
|
||||
measurement_system = field(
|
||||
Int16, offset=3255, default=0, documentation=
|
||||
"Measurement system: Highly recommended for all types of data. "
|
||||
"If Location Data stanzas are included in the file, this entry must agree with the Location Data stanza. "
|
||||
"If there is a disagreement, the last Location Data stanza is the controlling authority. "
|
||||
"1 = Meters, "
|
||||
"2 = Feet"
|
||||
)
|
||||
|
||||
impulse_signal_polarity = field(
|
||||
Int16, offset=3257, default=0, documentation=
|
||||
"Impulse signal polarity : "
|
||||
"1 = Increase in pressure or upward geophone case movement gives negative number on tape, "
|
||||
"2 = Increase in pressure or upward geophone case movement gives positive number on tape. "
|
||||
)
|
||||
|
||||
vibratory_polarity_code = field(
|
||||
Int16, offset=3259, default=0, documentation=
|
||||
"Vibratory polarity code: "
|
||||
"Seismic signal lags pilot signal by: "
|
||||
"1 = 337.5° to 22.5°, "
|
||||
"2 = 22.5° to 67.5°, "
|
||||
"3 = 67.5° to 112.5°, "
|
||||
"4 = 112.5° to 157.5°, "
|
||||
"5 = 157.5° to 202.5°, "
|
||||
"6 = 202.5°to 247.5°, "
|
||||
"7 = 247.5° to 292.5°, "
|
||||
"8 = 292.5° to 337.5°.")
|
||||
|
||||
format_revision_num = field(
|
||||
Int16, offset=3501, default=0x100, documentation=
|
||||
"SEG Y Format Revision Number. "
|
||||
"This is a 16-bit unsigned value with a Q- point between the first and second bytes. "
|
||||
"Thus for SEG Y Revision 1.0, as defined in this document, this will be recorded as 010016. This field is "
|
||||
"mandatory for all versions of SEG Y, although a value of zero indicates “traditional” SEG Y conforming to "
|
||||
"the 1975 standard."
|
||||
)
|
||||
|
||||
fixed_length_trace_flag = field(
|
||||
Int16, offset=3503, default=0, documentation=
|
||||
"Fixed length trace flag. A value of one indicates that all traces in this SEG Y file are guaranteed to have "
|
||||
"the same sample interval and number of samples, as specified in Textual File Header bytes 3217-3218 and "
|
||||
"3221-3222. A value of zero indicates that the length of the traces in the file may vary and the number of "
|
||||
"samples in bytes 115-116 of the Trace Header must be examined to determine the actual length of each trace. "
|
||||
"This field is mandatory for all versions of SEG Y, although a value of zero indicates “traditional” SEG Y "
|
||||
"conforming to the 1975 standard."
|
||||
)
|
||||
|
||||
num_extended_textual_headers = field(
|
||||
Int16, offset=3505, default=0, documentation=
|
||||
"Number of 3200-byte, Extended Textual File Header records following the Binary Header. "
|
||||
"A value of zero indicates there are no Extended Textual File Header records (i.e. this file has no Extended "
|
||||
"Textual File Header(s)). A value of -1 indicates that there are a variable number of Extended Textual File "
|
||||
"Header records and the end of the Extended Textual File Header is denoted by an ((SEG: EndText)) stanza in "
|
||||
"the final record. A positive value indicates that there are exactly that many Extended Textual File Header "
|
||||
"records. Note that, although the exact number of Extended Textual File Header records may be a useful piece "
|
||||
"of information, it will not always be known at the time the Binary Header is written and it is not mandatory "
|
||||
"that a positive value be recorded here. This field is mandatory for all versions of SEG Y, although a value "
|
||||
"of zero indicates “traditional” SEG Y conforming to the 1975 standard."
|
||||
)
|
||||
|
||||
@@ -0,0 +1,585 @@
|
||||
"""Catalogs are immutable mappings useful for building indexes.
|
||||
|
||||
This module contains definitions of many different catalog types,
|
||||
all of which implement the interface defined by the Catalog abstract
|
||||
base class, which is itself implements the mapping protocol.
|
||||
|
||||
Rather than constructing Catalog subtypes directly, prefer to use
|
||||
the CatalogBuilder class which will analyse the contents of the
|
||||
mapping to find a space and time efficient representation.
|
||||
"""
|
||||
|
||||
from collections import Mapping, Sequence, OrderedDict
|
||||
from fractions import Fraction
|
||||
import reprlib
|
||||
from segpy.sorted_set import SortedFrozenSet
|
||||
|
||||
from segpy.util import contains_duplicates, measure_stride, make_sorted_distinct_sequence
|
||||
|
||||
|
||||
class CatalogBuilder(object):
|
||||
"""Use a catalog builder to construct optimised, immutable mappings.
|
||||
|
||||
A CatalogBuilder is useful when, depending on the particular keys and
|
||||
values used, a more compact or efficient representation of the mapping
|
||||
is possible than, say, a regular dictionary. The CatalogBuilder
|
||||
accumulates values and then, once all values have been added, analyzes
|
||||
the keys and values to produce a more optimized representation of the
|
||||
mapping.
|
||||
"""
|
||||
|
||||
def __init__(self, mapping=None):
|
||||
"""Initialize a Catalog Builder.
|
||||
|
||||
Args:
|
||||
mapping: An optional mapping (such as a dictionary) of items.
|
||||
"""
|
||||
self._catalog = []
|
||||
if mapping is not None:
|
||||
for key, value in mapping.items():
|
||||
self.add(key, value)
|
||||
|
||||
def add(self, index, value):
|
||||
"""Add an item.
|
||||
|
||||
Each index must be unique if create() is to be subsequently
|
||||
called successfully, although duplicate index values will be
|
||||
accepted by this call without complaint.
|
||||
|
||||
"""
|
||||
self._catalog.append((index, value))
|
||||
|
||||
def create(self):
|
||||
"""Create a possibly more optimized representation of the mapping.
|
||||
|
||||
In this worst case, this method returns an object which is
|
||||
essentially an immutable dictionary. In the best case, the
|
||||
space savings can be vast.
|
||||
|
||||
Returns:
|
||||
A mapping, if a unique mapping from indexes to values is
|
||||
possible, otherwise None.
|
||||
|
||||
"""
|
||||
|
||||
# This method examines the contents of the mapping using
|
||||
# various heuristics to come up with a better representation.
|
||||
|
||||
if len(self._catalog) < 2:
|
||||
return DictionaryCatalog(self._catalog)
|
||||
|
||||
# In-place sort by index
|
||||
self._catalog.sort(key=lambda index_value: index_value[0])
|
||||
|
||||
if contains_duplicates(index for index, value in self._catalog):
|
||||
return None
|
||||
|
||||
if all(isinstance(index, Sequence) and (len(index) == 2)
|
||||
for index, value in self._catalog):
|
||||
return self._create_catalog_2()
|
||||
|
||||
return self._create_catalog_1()
|
||||
|
||||
def _create_catalog_1(self):
|
||||
"""Create a catalog for one-dimensional integer keys (i.e. scalars)
|
||||
"""
|
||||
index_min = self._catalog[0][0]
|
||||
index_max = self._catalog[-1][0]
|
||||
index_stride = measure_stride(index for index, value in self._catalog)
|
||||
assert index_stride != 0
|
||||
|
||||
value_start = self._catalog[0][1]
|
||||
value_stop = self._catalog[-1][1]
|
||||
value_stride = measure_stride(value for index, value in self._catalog)
|
||||
|
||||
if index_stride is None and value_stride is None:
|
||||
# Dictionary strategy - arbitrary keys and values
|
||||
return DictionaryCatalog(self._catalog)
|
||||
|
||||
if index_stride is not None and value_stride == 0:
|
||||
assert value_start == value_stop
|
||||
return RegularConstantCatalog(index_min,
|
||||
index_max,
|
||||
index_stride,
|
||||
value_start)
|
||||
|
||||
if index_stride is None and value_stride == 0:
|
||||
assert value_start == value_stop
|
||||
return ConstantCatalog(
|
||||
(index for index, value in self._catalog),
|
||||
value_start)
|
||||
|
||||
if index_stride is not None and value_stride is None:
|
||||
# Regular index - regular keys and arbitrary values
|
||||
return RegularCatalog(index_min,
|
||||
index_max,
|
||||
index_stride,
|
||||
(value for index, value in self._catalog))
|
||||
|
||||
if (index_stride is not None) and (value_stride is not None):
|
||||
assert value_stride != 0
|
||||
return LinearRegularCatalog(index_min,
|
||||
index_max,
|
||||
index_stride,
|
||||
value_start,
|
||||
value_stop,
|
||||
value_stride)
|
||||
|
||||
return DictionaryCatalog(self._catalog)
|
||||
|
||||
def _create_catalog_2(self):
|
||||
"""Create a catalog for two-dimensional integer keys.
|
||||
|
||||
Each key must be a two-element sequence.
|
||||
"""
|
||||
i_sorted = make_sorted_distinct_sequence(i for (i, j), value in self._catalog)
|
||||
j_sorted = make_sorted_distinct_sequence(j for (i, j), value in self._catalog)
|
||||
|
||||
i_is_regular = isinstance(i_sorted, range)
|
||||
j_is_regular = isinstance(j_sorted, range)
|
||||
|
||||
if i_is_regular and j_is_regular:
|
||||
is_rm, diff = self._is_row_major(i_sorted, j_sorted)
|
||||
if is_rm:
|
||||
return RowMajorCatalog2D(i_sorted, j_sorted, diff)
|
||||
|
||||
return DictionaryCatalog2D(i_sorted, j_sorted, self._catalog)
|
||||
|
||||
def _is_row_major(self, i_sorted, j_sorted):
|
||||
i_min = i_sorted[0]
|
||||
j_min = j_sorted[0]
|
||||
j_max = j_sorted[-1]
|
||||
diff = None
|
||||
for (i, j), actual_value in self._catalog:
|
||||
proposed_value = (i - i_min) * (j_max + 1 - j_min) + (j - j_min)
|
||||
current_diff = actual_value - proposed_value
|
||||
if diff is None:
|
||||
diff = current_diff
|
||||
if current_diff != diff:
|
||||
return False, None
|
||||
return True, diff
|
||||
|
||||
|
||||
class Catalog2D(Mapping):
|
||||
"""An abstract base class for 2D catalogs.
|
||||
"""
|
||||
|
||||
def __init__(self, i_range, j_range):
|
||||
"""Initialize a Catalog2D.
|
||||
|
||||
Args:
|
||||
i_range: A range which can generate all and only valid i indexes.
|
||||
j_range: A range which can generate all and only valid j indexes.
|
||||
"""
|
||||
self._i_range = i_range
|
||||
self._j_range = j_range
|
||||
|
||||
@property
|
||||
def i_range(self):
|
||||
return self._i_range
|
||||
|
||||
@property
|
||||
def j_range(self):
|
||||
return self._j_range
|
||||
|
||||
@property
|
||||
def i_min(self):
|
||||
"""Minimum i value"""
|
||||
return self._i_range[0]
|
||||
|
||||
@property
|
||||
def i_max(self):
|
||||
"""Maximum i value"""
|
||||
return self._i_range[-1]
|
||||
|
||||
@property
|
||||
def j_min(self):
|
||||
"""Minimum j value"""
|
||||
return self._j_range[0]
|
||||
|
||||
@property
|
||||
def j_max(self):
|
||||
"""Maximum j value"""
|
||||
return self._j_range[-1]
|
||||
|
||||
def key_min(self):
|
||||
"""Minimum (i, j) key"""
|
||||
return self.i_min, self.j_min
|
||||
|
||||
def key_max(self):
|
||||
"""Maximum (i, j) key"""
|
||||
return self.i_max, self.j_max
|
||||
|
||||
def value_start(self):
|
||||
"""Minimum value at key_min"""
|
||||
return self[self.key_min()]
|
||||
|
||||
def value_stop(self):
|
||||
"""Maximum value at key_max"""
|
||||
return self[self.key_max()]
|
||||
|
||||
|
||||
class RowMajorCatalog2D(Catalog2D):
|
||||
"""A mapping which assumes a row-major ordering of a two-dimensional matrix.
|
||||
|
||||
This is the ordering of items in a two-dimensional matrix where in
|
||||
the (i, j) key tuple the j value changes fastest when iterating
|
||||
through the items in order.
|
||||
|
||||
A RowMajorCatalog predicts the value v from the key (i, j) according to the
|
||||
following formula:
|
||||
|
||||
v = (i - i_min) * j_max + (j - j_min) + c
|
||||
|
||||
for
|
||||
i_min <= i <= i_max
|
||||
j_min <= j <= j_max
|
||||
|
||||
and where c is an integer constant to allow zero- or one-based indexing.
|
||||
"""
|
||||
|
||||
def __init__(self, i_range, j_range, constant):
|
||||
"""Initialize a RowMajorCatalog2D.
|
||||
|
||||
Args:
|
||||
i_range: A range which can generate all and only valid i indexes.
|
||||
j_range: A range which can generate all and only valid j indexes.
|
||||
constant: The constant offset used to produce the value.
|
||||
"""
|
||||
super().__init__(i_range, j_range)
|
||||
self._c = constant
|
||||
|
||||
@property
|
||||
def constant(self):
|
||||
return self._c
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError("{!r} key {!r} out of range".format(self, key))
|
||||
i, j = key
|
||||
value = (i - self.i_min) * (self.j_max + 1 - self.j_min) + (j - self.j_min) + self._c
|
||||
return value
|
||||
|
||||
def __contains__(self, key):
|
||||
return (key[0] in self._i_range) and \
|
||||
(key[1] in self._j_range)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._i_range) * len(self._j_range)
|
||||
|
||||
def __iter__(self):
|
||||
yield from ((i, j) for i in self._i_range for j in self._j_range)
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(i_range={}, j_range={}, c={})'.format(
|
||||
self.__class__.__name__,
|
||||
self.i_range, self.j_range, self._c)
|
||||
|
||||
|
||||
class DictionaryCatalog(Mapping):
|
||||
"""An immutable, ordered, dictionary mapping.
|
||||
"""
|
||||
|
||||
def __init__(self, items):
|
||||
self._items = OrderedDict(items)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._items[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._items)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._items)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._items
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(items={})'.format(
|
||||
self.__class__.__name__, reprlib.repr(self._items.items()))
|
||||
|
||||
|
||||
class DictionaryCatalog2D(Catalog2D):
|
||||
"""An immutable, ordered, dictionary mapping for 2D keys.
|
||||
"""
|
||||
|
||||
def __init__(self, i_range, j_range, items):
|
||||
super().__init__(i_range, j_range)
|
||||
self._items = OrderedDict(items)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._items[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._items)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._items)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._items
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(i_range={}, j_range={}, items={})'.format(
|
||||
self.i_range, self.j_range,
|
||||
self.__class__.__name__, reprlib.repr(self._items.items()))
|
||||
|
||||
|
||||
class RegularConstantCatalog(Mapping):
|
||||
"""Mapping with keys ordered with regular spacing along the number line.
|
||||
|
||||
The values associated with the keys are constant.
|
||||
"""
|
||||
|
||||
def __init__(self, key_min, key_max, key_stride, value):
|
||||
"""Initialize a RegularConstantCatalog.
|
||||
|
||||
The catalog is initialized by a description of how the keys
|
||||
are distributed along the number line, and a value which
|
||||
corresponds with all keys.
|
||||
|
||||
Args:
|
||||
key_min: The minimum key.
|
||||
key_max: The maximum key.
|
||||
key_stride: The difference between successive keys.
|
||||
value: A value associated with all keys.
|
||||
"""
|
||||
key_range = key_max - key_min
|
||||
if key_range % key_stride != 0:
|
||||
raise ValueError("RegularIndex key range {!r} is not "
|
||||
"a multiple of stride {!r}".format(
|
||||
key_stride, key_range))
|
||||
|
||||
self._key_min = key_min
|
||||
self._key_max = key_max
|
||||
self._key_stride = key_stride
|
||||
self._value = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError("{!r} does not contain key {!r}".format(self, key))
|
||||
return self._value
|
||||
|
||||
def __len__(self):
|
||||
return 1 + (self._key_max - self._key_min) / self._key_stride
|
||||
|
||||
def __contains__(self, key):
|
||||
return (self._key_min <= key <= self._key_max) and \
|
||||
((key - self._key_min) % self._key_stride == 0)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(range(self._key_min,
|
||||
self._key_max + 1,
|
||||
self._key_stride))
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(key_min={}, key_max={}, key_stride={}, value={})'.format(
|
||||
self.__class__.__name__,
|
||||
self._key_min,
|
||||
self._key_max,
|
||||
self._key_stride,
|
||||
self._value)
|
||||
|
||||
|
||||
class ConstantCatalog(Mapping):
|
||||
"""Mapping with arbitrary keys and a single constant value.
|
||||
"""
|
||||
|
||||
def __init__(self, keys, value):
|
||||
"""Initialize a RegularConstantCatalog.
|
||||
|
||||
The catalog is initialized by a description with an iterable series of
|
||||
keys and a constant value to be associated with all the keys.
|
||||
|
||||
Args:
|
||||
keys: An iterable series of distinct keys.
|
||||
key_max: The maximum key.
|
||||
key_stride: The difference between successive keys.
|
||||
value: A value associated with all keys.
|
||||
"""
|
||||
self._keys = SortedFrozenSet(keys)
|
||||
self._value = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError("{!r} does not contain key {!r}".format(self, key))
|
||||
return self._value
|
||||
|
||||
def __len__(self):
|
||||
return len(self._keys)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._keys
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._keys)
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(keys={}, value={})'.format(
|
||||
self.__class__.__name__,
|
||||
reprlib.repr(self._keys),
|
||||
self._value)
|
||||
|
||||
|
||||
class RegularCatalog(Mapping):
|
||||
"""Mapping with keys ordered with regular spacing along the number line.
|
||||
|
||||
The values associated with the keys are arbitrary.
|
||||
"""
|
||||
|
||||
def __init__(self, key_min, key_max, key_stride, values):
|
||||
"""Initialize a RegularCatalog.
|
||||
|
||||
The catalog is initialized by a description of how the keys
|
||||
are distributed along the number line, and an iterable series of
|
||||
corresponding values.
|
||||
|
||||
Args:
|
||||
key_min: The minimum key.
|
||||
key_max: The maximum key.
|
||||
key_stride: The difference between successive keys.
|
||||
values: An iterable series of values corresponding to the keys.
|
||||
|
||||
Raises:
|
||||
ValueError: There is any inconsistency in the keys, stride,
|
||||
and/or values.
|
||||
"""
|
||||
key_range = key_max - key_min
|
||||
if key_range % key_stride != 0:
|
||||
raise ValueError("{} key range {!r} is not "
|
||||
"a multiple of stride {!r}".format(self.__class__.__name__,
|
||||
key_stride, key_range))
|
||||
self._key_min = key_min
|
||||
self._key_max = key_max
|
||||
self._key_stride = key_stride
|
||||
self._values = list(values)
|
||||
num_keys = 1 + key_range // key_stride
|
||||
if num_keys != len(self._values):
|
||||
raise ValueError("{} key range and values inconsistent".format(self.__class__.__name__))
|
||||
|
||||
def __getitem__(self, key):
|
||||
if not (self._key_min <= key <= self._key_max):
|
||||
raise KeyError("{!r} key {!r} out of range".format(self, key))
|
||||
offset = key - self._key_min
|
||||
if offset % self._key_stride != 0:
|
||||
raise KeyError("{!r} does not contain key {!r}".format(self, key))
|
||||
index = offset // self._key_stride
|
||||
return self._values[index]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._values)
|
||||
|
||||
def __contains__(self, key):
|
||||
return (self._key_min <= key <= self._key_max) and \
|
||||
((key - self._key_min) % self._key_stride == 0)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(range(self._key_min,
|
||||
self._key_max + 1,
|
||||
self._key_stride))
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(key_min={}, key_max={}, key_stride={}, values={})'.format(
|
||||
self.__class__.__name__,
|
||||
self._key_min,
|
||||
self._key_max,
|
||||
self._key_stride,
|
||||
reprlib.repr(self._values))
|
||||
|
||||
|
||||
class LinearRegularCatalog(Mapping):
|
||||
"""A mapping which assumes a linear relationship between keys and values.
|
||||
|
||||
A LinearRegularCatalog predicts the value v from the key according to the
|
||||
following formula:
|
||||
|
||||
v = (value_max - value_min) / (key_max - key_min) * (key - key_min) + value_min
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
key_min,
|
||||
key_max,
|
||||
key_stride,
|
||||
value_start,
|
||||
value_stop,
|
||||
value_stride):
|
||||
"""Initialize a LinearRegularCatalog.
|
||||
|
||||
Args:
|
||||
key_min: The minimum key.
|
||||
key_max: The maximum key.
|
||||
key_stride: The difference between successive keys.
|
||||
value_start: The value corresponding to the minimum key.
|
||||
value_stop: The value corresponding to the maximum key.
|
||||
value_stride:
|
||||
|
||||
Raises:
|
||||
ValueError: There is any inconsistency in the keys, strides,
|
||||
and/or values.
|
||||
"""
|
||||
key_range = key_max - key_min
|
||||
if key_range % key_stride != 0:
|
||||
raise ValueError("{} key range {!r} is not "
|
||||
"a multiple of key stride {!r}".format(
|
||||
self.__class__.__name__,
|
||||
key_stride,
|
||||
key_range))
|
||||
self._key_stride = key_stride
|
||||
|
||||
value_range = value_stop - value_start
|
||||
if value_range % value_stride != 0:
|
||||
raise ValueError("{} value range {!r} is not "
|
||||
"a multiple of value stride {!r}".format(
|
||||
self.__class__.__name__,
|
||||
value_stride,
|
||||
value_range))
|
||||
self._value_stride = value_stride
|
||||
|
||||
self._key_min = key_min
|
||||
self._key_max = key_max
|
||||
self._value_start = value_start
|
||||
self._value_stop = value_stop
|
||||
|
||||
num_keys = 1 + (self._key_max - self._key_min) // self._key_stride
|
||||
num_values = 1 + (self._value_stop - self._value_start) // self._value_stride
|
||||
if num_keys != num_values:
|
||||
raise ValueError("{} inconsistent number of "
|
||||
"keys {} and values {}".format(
|
||||
self.__class__.__name__,
|
||||
num_keys,
|
||||
num_values))
|
||||
|
||||
self._m = Fraction(self._value_stop - self._value_start,
|
||||
self._key_max - self._key_min)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if not (self._key_min <= key <= self._key_max):
|
||||
raise KeyError("{!r} key {!r} out of range".format(self, key))
|
||||
offset = key - self._key_min
|
||||
if offset % self._key_stride != 0:
|
||||
raise KeyError("{!r} does not contain key {!r}".format(self, key))
|
||||
|
||||
v = self._m * (key - self._key_min) + self._value_start
|
||||
assert v.denominator == 1
|
||||
return v.numerator
|
||||
|
||||
def __len__(self):
|
||||
return 1 + (self._key_max - self._key_min) // self._key_stride
|
||||
|
||||
def __contains__(self, key):
|
||||
return (self._key_min <= key <= self._key_max) and \
|
||||
((key - self._key_min) % self._key_stride == 0)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(range(self._key_min, self._key_max + 1, self._key_stride))
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(key_min={}, key_max={}, key_stride={}, value_start={}, value_stop={}, value_stride={})'.format(
|
||||
self.__class__.__name__,
|
||||
self._key_min,
|
||||
self._key_max,
|
||||
self._key_stride,
|
||||
self._value_start,
|
||||
self._value_stop,
|
||||
self._value_stride)
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Mappings between the coding systems used for sample types.
|
||||
"""
|
||||
|
||||
# A mapping from data sample format codes to SEG Y types.
|
||||
DATA_SAMPLE_FORMAT_TO_SEG_Y_TYPE = {
|
||||
1: 'ibm',
|
||||
2: 'int32',
|
||||
3: 'int16',
|
||||
5: 'float32',
|
||||
8: 'int8'}
|
||||
|
||||
# A mapping from SEG Y data types to format characters used by the
|
||||
# Python Standard Library struct module
|
||||
SEG_Y_TYPE_TO_CTYPE = {
|
||||
'int32': 'i',
|
||||
'uint32': 'I',
|
||||
'int16': 'h',
|
||||
'uint16': 'H',
|
||||
'int8': 'b',
|
||||
'uint8': 'B',
|
||||
'float32': 'f',
|
||||
'ibm': 'ibm'}
|
||||
|
||||
|
||||
# Human readable descriptions of the sample types.
|
||||
SEG_Y_TYPE_DESCRIPTION = {
|
||||
'ibm': 'IBM 32 bit float',
|
||||
'int32': '32 bit signed integer',
|
||||
'uint32': '32 bit unsigned integer',
|
||||
'int16': '16 bit signed integer',
|
||||
'uint16': '16 bit unsigned integer',
|
||||
'float32': 'IEEE float32',
|
||||
'int8': '8 bit signed integer (byte)',
|
||||
'uint8': '8 bit unsigned integer (byte)'}
|
||||
|
||||
# Sizes of various ctypes in bytes
|
||||
CTYPE_TO_SIZE = dict(
|
||||
i=4,
|
||||
I=4,
|
||||
h=2,
|
||||
H=2,
|
||||
b=1,
|
||||
B=1,
|
||||
f=4,
|
||||
ibm=4)
|
||||
|
||||
|
||||
def size_in_bytes(ctype):
|
||||
"""The size in bytes of a ctype.
|
||||
"""
|
||||
return CTYPE_TO_SIZE[ctype]
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
"""Property decorator for the `__doc__` attribute.
|
||||
|
||||
Useful for when you want a custom docstring for class instances
|
||||
while still showing a generic docstring for the class itself.
|
||||
|
||||
A naive attempt using `@property` generally breaks Sphinx as
|
||||
`cls.__doc__` returns the property object itself, and not a string.
|
||||
|
||||
See the documentation for `docstring_property` for an example.
|
||||
"""
|
||||
|
||||
|
||||
def docstring_property(class_doc):
|
||||
"""Property attribute for docstrings.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
>>> class A(object):
|
||||
... '''Main docstring'''
|
||||
... def __init__(self, x):
|
||||
... self.x = x
|
||||
... @docstring_property(__doc__)
|
||||
... def __doc__(self):
|
||||
... return "My value of x is %s." % self.x
|
||||
|
||||
>>> A.__doc__
|
||||
'Main docstring'
|
||||
|
||||
>>> a = A(10)
|
||||
>>> a.__doc__
|
||||
'My value of x is 10.'
|
||||
"""
|
||||
def wrapper(fget):
|
||||
return DocstringProperty(class_doc, fget)
|
||||
return wrapper
|
||||
|
||||
|
||||
class DocstringProperty(object):
|
||||
"""Property for the `__doc__` attribute.
|
||||
|
||||
Different than `property` in the following two ways:
|
||||
|
||||
* When the attribute is accessed from the main class, it returns the value
|
||||
of `class_doc`, *not* the property itself. This is necessary so Sphinx
|
||||
and other documentation tools can access the class docstring.
|
||||
|
||||
* Only supports getting the attribute; setting and deleting raise an
|
||||
`AttributeError`.
|
||||
"""
|
||||
|
||||
def __init__(self, class_doc, fget):
|
||||
self.class_doc = class_doc
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self.class_doc
|
||||
else:
|
||||
return self.fget(obj)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
raise AttributeError("can't set attribute")
|
||||
|
||||
def __delete__(self, obj):
|
||||
raise AttributeError("can't delete attribute")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Support for the text encodings used in SEG Y.
|
||||
|
||||
SEG Y data can contain textual data encoded in either ASCII
|
||||
or EBCDIC format. This module contains tools for detecting those
|
||||
encodings.
|
||||
"""
|
||||
|
||||
ASCII = 'ascii'
|
||||
EBCDIC = 'cp037'
|
||||
|
||||
SUPPORTED_ENCODINGS = (ASCII, EBCDIC)
|
||||
|
||||
|
||||
class UnsupportedEncodingError(Exception):
|
||||
|
||||
def __init__(self, text, encoding):
|
||||
self._encoding = encoding
|
||||
super(UnsupportedEncodingError, self).__init__(text)
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return self._encoding
|
||||
|
||||
def __str__(self):
|
||||
return "{} not supported for encoding {}".format(self.args[0], self._encoding)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({!r}, {!r}".format(self.__class__.__name__, self.args[0], self._encoding)
|
||||
|
||||
|
||||
def is_supported_encoding(encoding):
|
||||
return encoding in SUPPORTED_ENCODINGS
|
||||
|
||||
|
||||
COMMON_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789:_- '
|
||||
COMMON_EBCDIC_CHARS = set(COMMON_CHARS.encode(EBCDIC))
|
||||
COMMON_ASCII_CHARS = set(COMMON_CHARS.encode(ASCII))
|
||||
|
||||
|
||||
def guess_encoding(bs, threshold=0.5):
|
||||
"""Try to determine whether the encoding of byte stream b is an ASCII string or an EBCDIC string.
|
||||
|
||||
Args:
|
||||
bs: A byte string (Python 2 - str; Python 3 - bytes)
|
||||
|
||||
Returns:
|
||||
A string which can be used with the Python encoding functions: 'cp037' for EBCDIC, 'ascii' for ASCII or None
|
||||
if neither.
|
||||
"""
|
||||
|
||||
ebcdic_count = 0
|
||||
ascii_count = 0
|
||||
null_count = 0
|
||||
|
||||
count = 0
|
||||
for b in bs:
|
||||
if b in COMMON_EBCDIC_CHARS:
|
||||
ebcdic_count +=1
|
||||
if b in COMMON_ASCII_CHARS:
|
||||
ascii_count +=1
|
||||
if b == 0:
|
||||
null_count += 1
|
||||
count += 1
|
||||
|
||||
if count == 0:
|
||||
return None
|
||||
|
||||
ebcdic_freq = ebcdic_count / count
|
||||
ascii_freq = ascii_count / count
|
||||
null_freq = null_count / count
|
||||
|
||||
if null_freq == 1.0:
|
||||
return ASCII # Doesn't matter
|
||||
|
||||
if ebcdic_freq < threshold <= ascii_freq:
|
||||
return ASCII
|
||||
|
||||
if ebcdic_freq >= threshold > ascii_freq:
|
||||
return EBCDIC
|
||||
|
||||
if ebcdic_freq < threshold and ascii_freq < threshold:
|
||||
return None
|
||||
|
||||
return None
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
from collections import OrderedDict
|
||||
from weakref import WeakKeyDictionary
|
||||
from itertools import chain
|
||||
|
||||
from segpy import __version__
|
||||
from segpy.docstring import docstring_property
|
||||
from segpy.util import underscores_to_camelcase, first_sentence, super_class, collect_attributes
|
||||
|
||||
|
||||
class Header:
|
||||
"""An abstract base class for header format definitions."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialise a header instance.
|
||||
|
||||
Args:
|
||||
*args: Positional arguments are matched with header fields in the order they
|
||||
are declared in the class definition (i.e. the same order defined by
|
||||
the ordered_field_names() method. From a performance perspective
|
||||
positional arguments are faster than keyword arguments.
|
||||
|
||||
**kwargs: Keyword arguments are assigned to the header field of the same name.
|
||||
Keyword argument values will overwrite any positional argument values.
|
||||
|
||||
Raises:
|
||||
TypeError: If keyword argument names do not correspond to header fields.
|
||||
"""
|
||||
for keyword, arg in zip(self.ordered_field_names(), args):
|
||||
setattr(self, keyword, arg)
|
||||
|
||||
for keyword, arg in kwargs.items():
|
||||
try:
|
||||
getattr(self, keyword)
|
||||
except AttributeError as e:
|
||||
raise TypeError("{!r} is not a recognised field name for {!r}"
|
||||
.format(keyword, self.__class__.__name__)) from e
|
||||
else:
|
||||
setattr(self, keyword, arg)
|
||||
|
||||
_ordered_field_names = tuple()
|
||||
|
||||
@classmethod
|
||||
def ordered_field_names(cls):
|
||||
"""The ordered list of field names.
|
||||
|
||||
This is a metamethod which should be called on cls.
|
||||
|
||||
Returns:
|
||||
An tuple containing the field names in order.
|
||||
"""
|
||||
|
||||
if cls is Header:
|
||||
return cls._ordered_field_names
|
||||
return super_class(cls).ordered_field_names() + cls._ordered_field_names
|
||||
|
||||
def __getattr__(self, name):
|
||||
raise AttributeError("Object of type {!r} has no attribute {!r}".format(self.__class__.__name__, name))
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({})".format(
|
||||
self.__class__.__name__,
|
||||
', '.join("{}={}".format(k, getattr(self, k)) for k in self.ordered_field_names()))
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state['__version__'] = __version__
|
||||
state['_all_attributes'] = OrderedDict((name, getattr(self, name)) for name in self._ordered_field_names)
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
if state['__version__'] != __version__:
|
||||
raise TypeError("Cannot unpickle {} version {} into version {}"
|
||||
.format(self.__class__.__name__,
|
||||
state['__version__'],
|
||||
__version__))
|
||||
del state['__version__']
|
||||
|
||||
for name, value in state['_all_attributes'].items():
|
||||
setattr(self, name, value)
|
||||
del state['_all_attributes']
|
||||
self.__dict__.update(state)
|
||||
|
||||
def __iter__(self):
|
||||
for name in self._ordered_field_names:
|
||||
yield name,getattr(self, name)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
setattr(self, key, item)
|
||||
|
||||
def are_equal(self, other):
|
||||
"""Compare two headers for equality.
|
||||
|
||||
Note:
|
||||
This is not implemented as __eq__() to prevent recursive behaviour in the header descriptor.
|
||||
"""
|
||||
if type(self) != type(other):
|
||||
return False
|
||||
return all(getattr(self, field_name) == getattr(other, field_name) for field_name in self.ordered_field_names())
|
||||
|
||||
|
||||
class FormatMeta(type):
|
||||
"""A metaclass for header format classes.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def __prepare__(mcs, name, bases, *args, **kwargs):
|
||||
return OrderedDict()
|
||||
|
||||
def __new__(mcs, name, bases, namespace):
|
||||
|
||||
# TODO: This is a good point to validate that the fields are in order and that the
|
||||
# TODO: format specification is valid. We shouldn't even build the class otherwise.
|
||||
|
||||
# TODO: Also validate existence of LENGTH_IN_BYTES
|
||||
|
||||
namespace['_ordered_field_names'] = tuple(name for name, attr in namespace.items()
|
||||
if isinstance(attr, HeaderFieldDescriptor))
|
||||
|
||||
transitive_bases = set(chain.from_iterable(type(base).mro(base) for base in bases))
|
||||
|
||||
if Header not in transitive_bases:
|
||||
bases = (Header,) + bases
|
||||
|
||||
for attr_name, attr in namespace.items():
|
||||
|
||||
# This shenanigans is necessary so we can have all the following work is a useful way
|
||||
# help(class), help(instance), help(class.property) and help(instance.property)
|
||||
|
||||
# Set the _name attribute of the field instance if it hasn't already been set
|
||||
if isinstance(attr, HeaderFieldDescriptor):
|
||||
if attr._name is None:
|
||||
attr._name = attr_name
|
||||
|
||||
# We rename the *class* and set its docstring so help() works usefully
|
||||
# when called with a class containing such fields.
|
||||
attr_class = attr.__class__
|
||||
if issubclass(attr_class, NamedField) and attr_class is not NamedField:
|
||||
attr_class.__name__ = underscores_to_camelcase(attr_name)
|
||||
attr_class.__doc__ = attr.documentation
|
||||
|
||||
return super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
|
||||
def is_public_non_field_attr(name, attr):
|
||||
return (not name.startswith('_')) and (not isinstance(attr, HeaderFieldDescriptor) and (not isinstance(attr, classmethod)))
|
||||
|
||||
|
||||
class SubFormatMeta(FormatMeta):
|
||||
"""A metaclass for a format class which has a subset of the fields in an existing format class.
|
||||
|
||||
SubFormat classes can be used to reduce storage requirements and increase performance, since they can be
|
||||
used to generate simpler HeaderPackers.
|
||||
|
||||
SubFormat classes must be declared as:
|
||||
|
||||
class MySubFormat(metaclass=SubFormatMeta,
|
||||
parent_format=MyFormatClass,
|
||||
parent_field_names=[
|
||||
'first_field_name',
|
||||
'second_field_name']):
|
||||
pass
|
||||
"""
|
||||
|
||||
def __new__(mcs, name, bases, namespace, parent_format, parent_field_names):
|
||||
"""
|
||||
Args:
|
||||
name: The name of the actual class being created by this metaclass.
|
||||
bases: The base classes of the actual class.
|
||||
parent_format: An existing Format (?Header) on which (sort out terminology here)
|
||||
of which this format has a subset of fields.
|
||||
parent_field_names: An iterable series of field names which this format should
|
||||
duplicate from the parent_format.
|
||||
"""
|
||||
|
||||
# Copy the requested fields, by creating a new descriptor based
|
||||
# on information retrieved from the existing descriptor
|
||||
for field_name in parent_field_names:
|
||||
named_field = getattr(parent_format, field_name)
|
||||
assert named_field.name == field_name
|
||||
field_copy = field(named_field.value_type,
|
||||
named_field.offset,
|
||||
named_field.default,
|
||||
named_field.documentation)
|
||||
namespace[field_name] = field_copy
|
||||
|
||||
# Copy other non-field class attributes
|
||||
non_field_attributes = list(collect_attributes(parent_format, Header, is_public_non_field_attr))
|
||||
namespace.update((name, value) for _, name, value in non_field_attributes)
|
||||
|
||||
# Add a reference back to the original format
|
||||
namespace['_parent_format'] = parent_format
|
||||
|
||||
return super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
def __init__(mcs, name, bases, namespace, parent_format, parent_field_names):
|
||||
# Absorb the additional arguments
|
||||
super().__init__(name, bases, namespace)
|
||||
|
||||
|
||||
class NamedField:
|
||||
"""Instances of NamedField can be detected by the NamedDescriptorResolver metaclass."""
|
||||
|
||||
def __init__(self, value_type, offset, default, documentation):
|
||||
self._name = None # Set later by the metaclass
|
||||
self._value_type = value_type
|
||||
self._offset = int(offset)
|
||||
self._default = self._value_type(default)
|
||||
self._documentation = str(documentation)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"The field name."
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def value_type(self):
|
||||
"The field value type (e.g. Int32)"
|
||||
return self._value_type
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
"The offset in bytes from the beginning of the header."
|
||||
return self._offset
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
"The default value of the field. Must be convertible to value_type."
|
||||
return self._default
|
||||
|
||||
@property
|
||||
def documentation(self):
|
||||
"A descriptive text string."
|
||||
return self._documentation
|
||||
|
||||
@docstring_property(__doc__)
|
||||
def __doc__(self):
|
||||
return first_sentence(self._documentation)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}(name={!r}, value_type={!r}, offset={!r}, default={!r})".format(
|
||||
self.__class__.__name__,
|
||||
self.name,
|
||||
self.value_type.__name__,
|
||||
self.offset,
|
||||
self.default)
|
||||
|
||||
|
||||
def field(value_type, offset, default, documentation):
|
||||
"""
|
||||
Args:
|
||||
value_type: The type of the field (e.g. Int32)
|
||||
|
||||
offset: The offset in bytes for this field from the start of the header.
|
||||
|
||||
default: The default value for this field.
|
||||
|
||||
documentation: A docstring for the field. The first sentence should be usable
|
||||
as a brief description.
|
||||
|
||||
Returns:
|
||||
An instance of a subclass of NamedField class.
|
||||
"""
|
||||
|
||||
# Create a class specifically for this field. This class will later get
|
||||
# renamed when the NamedDescriptorMangler metaclass does its job, to
|
||||
# a class name based on the field name.
|
||||
|
||||
class SpecificField(HeaderFieldDescriptor):
|
||||
pass
|
||||
|
||||
return SpecificField(value_type, offset, default, documentation)
|
||||
|
||||
|
||||
class HeaderFieldDescriptor:
|
||||
|
||||
def __init__(self, value_type, offset, default, documentation):
|
||||
self._named_field = NamedField(value_type, offset, default, documentation)
|
||||
self._instance_data = WeakKeyDictionary()
|
||||
|
||||
@property
|
||||
def _name(self):
|
||||
return self._named_field.name
|
||||
|
||||
@_name.setter
|
||||
def _name(self, value):
|
||||
self._named_field._name = value
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
"""Retrieve the format or instance data.
|
||||
|
||||
When called on the class we return a NamedField instance containing the format data. For example:
|
||||
|
||||
line_seq_num_default = TraceHeaderRev1.line_sequence_num.default
|
||||
line_seq_num_offset = TraceHeaderRev1.line_sequence_num.offset
|
||||
|
||||
When called on an instance we return the field value.
|
||||
|
||||
line_seq_num = my_trace_header.line_sequence_num
|
||||
"""
|
||||
if instance is None:
|
||||
return self._named_field
|
||||
if instance not in self._instance_data:
|
||||
return self._named_field.default
|
||||
return self._instance_data[instance]
|
||||
|
||||
def __set__(self, instance, value):
|
||||
"""Set the field value."""
|
||||
try:
|
||||
self._instance_data[instance] = self._named_field._value_type(value)
|
||||
except ValueError as e:
|
||||
raise ValueError("Assigned value {!r} for {} attribute must be convertible to {}: {}"
|
||||
.format(value, self._name, self._named_field._value_type.__name__, e)) from e
|
||||
|
||||
def __delete__(self, instance):
|
||||
raise AttributeError("Can't delete {} attribute".format(self._name))
|
||||
|
||||
@docstring_property(__doc__)
|
||||
def __doc__(self):
|
||||
return self._named_field._documentation
|
||||
|
||||
# TODO: Get documentation of these descriptors working correctly
|
||||
@@ -0,0 +1,441 @@
|
||||
from math import frexp, isnan, isinf, ceil, floor, trunc
|
||||
from numbers import Real
|
||||
|
||||
from segpy.util import four_bytes
|
||||
|
||||
|
||||
|
||||
IBM_ZERO_BYTES = b'\x00\x00\x00\x00'
|
||||
IBM_NEGATIVE_ONE_BYTES = b'\xc1\x10\x00\x00'
|
||||
IBM_POSITIVE_ONE_BYTES = b'A\x10\x00\x00'
|
||||
|
||||
MIN_IBM_FLOAT = -7.2370051459731155e+75
|
||||
LARGEST_NEGATIVE_NORMAL_IBM_FLOAT = -5.397605346934028e-79
|
||||
SMALLEST_POSITIVE_NORMAL_IBM_FLOAT = 5.397605346934028e-79
|
||||
MAX_IBM_FLOAT = 7.2370051459731155e+75
|
||||
|
||||
MAX_BITS_PRECISION_IBM_FLOAT = 24
|
||||
MIN_BITS_PRECISION_IBM_FLOAT = 21 # The first 3 bits of the mantissa may be zero
|
||||
EPSILON_IBM_FLOAT = pow(2.0, -(MIN_BITS_PRECISION_IBM_FLOAT - 1))
|
||||
_L24 = 2 ** MAX_BITS_PRECISION_IBM_FLOAT
|
||||
_F24 = float(pow(2, MAX_BITS_PRECISION_IBM_FLOAT))
|
||||
|
||||
_L21 = 2 ** MIN_BITS_PRECISION_IBM_FLOAT
|
||||
|
||||
EXPONENT_BIAS = 64
|
||||
|
||||
MIN_EXACT_INTEGER_IBM_FLOAT = -2**MAX_BITS_PRECISION_IBM_FLOAT
|
||||
MAX_EXACT_INTEGER_IBM_FLOAT = 2**MIN_BITS_PRECISION_IBM_FLOAT
|
||||
|
||||
|
||||
def ibm2ieee(big_endian_bytes):
|
||||
"""Interpret a byte string as a big-endian IBM float.
|
||||
|
||||
Args:
|
||||
big_endian_bytes (str): A string containing at least four bytes.
|
||||
|
||||
Returns:
|
||||
The floating point value.
|
||||
"""
|
||||
a, b, c, d = four_bytes(big_endian_bytes)
|
||||
|
||||
if a == b == c == d == 0:
|
||||
return 0.0
|
||||
|
||||
sign = -1 if (a & 0x80) else 1
|
||||
exponent_16_biased = a & 0x7f
|
||||
mantissa = ((b << 16) | (c << 8) | d) / _F24
|
||||
|
||||
value = sign * mantissa * pow(16, exponent_16_biased - EXPONENT_BIAS)
|
||||
return value
|
||||
|
||||
|
||||
BITS_PER_NYBBLE = 4
|
||||
|
||||
|
||||
def ieee2ibm(f):
|
||||
"""Convert a float to four big-endian bytes representing an IBM float.
|
||||
|
||||
Args:
|
||||
f (float): The value to be converted.
|
||||
|
||||
Returns:
|
||||
A bytes object (Python 3) or a string (Python 2) containing four
|
||||
bytes representing a big-endian IBM float.
|
||||
|
||||
Raises:
|
||||
OverflowError: If f is outside the representable range.
|
||||
ValueError: If f is NaN or infinite.
|
||||
FloatingPointError: If f cannot be represented without total loss of precision.
|
||||
"""
|
||||
if f == 0:
|
||||
# There are many potential representations of zero - this is the standard one
|
||||
return b'\x00\x00\x00\x00'
|
||||
|
||||
if isnan(f):
|
||||
raise ValueError("NaN cannot be represented in IBM floating point")
|
||||
|
||||
if isinf(f):
|
||||
raise ValueError("Infinities cannot be represented in IBM floating point")
|
||||
|
||||
if f < MIN_IBM_FLOAT:
|
||||
raise OverflowError("IEEE Floating point value {} is less than the "
|
||||
"representable minimum for IBM floats.".format(f))
|
||||
|
||||
if f > MAX_IBM_FLOAT:
|
||||
raise OverflowError("IEEE Floating point value {} is greater than the "
|
||||
"representable maximum for IBM floats".format(f))
|
||||
|
||||
# Now compute m and e to satisfy:
|
||||
#
|
||||
# f = m * 2^e
|
||||
#
|
||||
# where 0.5 <= abs(m) < 1
|
||||
# except when f == 0 in which case m == 0 and e == 0, which we've already
|
||||
# dealt with.
|
||||
m, e = frexp(f)
|
||||
|
||||
# Convert the fraction (m) into an integer representation. IEEE float32
|
||||
# numbers have 23 explicit (24 implicit) bits of precision.
|
||||
|
||||
mantissa = abs(int(m * _L24))
|
||||
exponent = e
|
||||
sign = 0x80 if f < 0 else 0x00
|
||||
|
||||
# IBM single precision floats are of the form
|
||||
# (-1)^sign * 0.significand * 16^(exponent-64)
|
||||
|
||||
# Adjust the exponent, and the mantissa in sympathy so it is
|
||||
# a multiple of four, so it can be expressed in base 16
|
||||
remainder = exponent % 4
|
||||
if remainder != 0:
|
||||
shift = 4 - remainder
|
||||
mantissa >>= shift
|
||||
exponent += shift
|
||||
|
||||
exponent_16 = exponent >> 2 # Divide by four to convert to base 16
|
||||
exponent_16_biased = exponent_16 + 64 # Add the exponent bias of 64
|
||||
|
||||
# If the biased exponent is negative, we try to use a subnormal representation
|
||||
if exponent_16_biased < 0:
|
||||
shift_16 = 0 - exponent_16_biased
|
||||
exponent_16_biased += shift_16 # An increment of the base-16 exponent must be balanced by
|
||||
mantissa >>= 4 * shift_16 # A division by 16 (four binary places) in the mantissa
|
||||
if mantissa == 0:
|
||||
raise FloatingPointError("IEEE Floating point value {} is smaller than the "
|
||||
"smallest subnormal number for IBM floats.".format(f))
|
||||
|
||||
a = sign | exponent_16_biased
|
||||
b = (mantissa >> 16) & 0xff
|
||||
c = (mantissa >> 8) & 0xff
|
||||
d = mantissa & 0xff
|
||||
|
||||
return bytes((a, b, c, d))
|
||||
|
||||
|
||||
class IBMFloat(Real):
|
||||
|
||||
__slots__ = ['_data']
|
||||
|
||||
def __new__(cls, b):
|
||||
obj = object.__new__(cls)
|
||||
|
||||
data = bytes(b)
|
||||
num_bytes = len(data)
|
||||
if num_bytes != 4:
|
||||
raise ValueError("{} cannot be constructed from {} values".format(cls.__name__, num_bytes))
|
||||
obj._data = data
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def from_float(cls, f):
|
||||
"""Construct an IBMFloat from an IEEE float.
|
||||
|
||||
Args:
|
||||
f (float): The value to be converted.
|
||||
|
||||
Returns:
|
||||
An IBMFloat.
|
||||
|
||||
Raises:
|
||||
OverflowError: If f is outside the representable range.
|
||||
ValueError: If f is NaN or infinite.
|
||||
FloatingPointError: If f cannot be represented without total loss of precision.
|
||||
"""
|
||||
return cls(ieee2ibm(f))
|
||||
|
||||
@classmethod
|
||||
def from_real(cls, f):
|
||||
if isinstance(f, IBMFloat):
|
||||
return f
|
||||
return cls.from_float(f)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, b):
|
||||
return cls(b)
|
||||
|
||||
@classmethod
|
||||
def ldexp(cls, fraction, exponent):
|
||||
"""Make an IBMFloat from fraction and exponent.
|
||||
|
||||
The is the inverse function of IBMFloat.frexp()
|
||||
|
||||
Args:
|
||||
fraction: A Real in the range -1.0 to 1.0.
|
||||
exponent: An integer in the range -256 to 255 inclusive.
|
||||
"""
|
||||
if not (-1.0 <= fraction <= 1.0):
|
||||
raise ValueError("ldexp fraction {!r} out of range -1.0 to +1.0")
|
||||
|
||||
if not (-256 <= exponent < 256):
|
||||
raise ValueError("ldexp exponent {!r} out of range -256 to 256")
|
||||
|
||||
ieee = fraction * 2**exponent
|
||||
return IBMFloat.from_float(ieee)
|
||||
|
||||
@property
|
||||
def signbit(self):
|
||||
"""True if the value is negative, otherwise False."""
|
||||
return bool(self._data[0] & 0x80)
|
||||
|
||||
def __float__(self):
|
||||
return ibm2ieee(self._data)
|
||||
|
||||
def __bytes__(self):
|
||||
return self._data
|
||||
|
||||
def __repr__(self):
|
||||
return "{}.from_float({!r}) ~{!r}".format(self.__class__.__name__, self._data, float(self))
|
||||
|
||||
def __str__(self):
|
||||
return str(float(self))
|
||||
|
||||
def __bool__(self):
|
||||
return not self.is_zero()
|
||||
|
||||
def is_zero(self):
|
||||
return self.int_mantissa == 0
|
||||
|
||||
def __nonzero__(self):
|
||||
return not self.is_zero()
|
||||
|
||||
def is_subnormal(self):
|
||||
if self.is_zero():
|
||||
# Only one of the many possible representations of zero is considered 'normal' - all the zeros
|
||||
return not all(b == 0 for b in self._data)
|
||||
|
||||
return self._data[1] < 16 # TODO: Replace magic number with constant
|
||||
|
||||
def zero_subnormal(self):
|
||||
return IBM_FLOAT_ZERO if self.is_subnormal() else self
|
||||
|
||||
def frexp(self):
|
||||
"""Obtain the fraction and exponent.
|
||||
|
||||
Returns:
|
||||
A pair where the first item is the fraction in the range -1.0 and +1.0 and the
|
||||
exponent is an integer such that f = fraction * 2**exponent
|
||||
"""
|
||||
sign = -1 if self.signbit else 1
|
||||
mantissa = sign * self.int_mantissa / _F24
|
||||
exp_2 = self.exp16 * 4
|
||||
return mantissa, exp_2
|
||||
|
||||
def __pos__(self):
|
||||
return self
|
||||
|
||||
def __neg__(self):
|
||||
if self.is_zero():
|
||||
return IBM_FLOAT_ZERO
|
||||
|
||||
data = self._data
|
||||
return IBMFloat((data[0] ^ 0b10000000,
|
||||
data[1],
|
||||
data[2],
|
||||
data[3]))
|
||||
|
||||
def __abs__(self):
|
||||
if self.is_zero():
|
||||
return IBM_FLOAT_ZERO
|
||||
|
||||
data = self._data
|
||||
return IBMFloat((data[0] & 0b01111111,
|
||||
data[1],
|
||||
data[2],
|
||||
data[3]))
|
||||
|
||||
def __eq__(self, rhs):
|
||||
lhs = self
|
||||
|
||||
if not isinstance(rhs, IBMFloat):
|
||||
return float(lhs) == float(rhs)
|
||||
|
||||
lhs_sign = lhs.signbit
|
||||
rhs_sign = rhs.signbit
|
||||
|
||||
if lhs_sign != rhs_sign:
|
||||
return False
|
||||
|
||||
nlhs = lhs.normalize()
|
||||
nrhs = rhs.normalize()
|
||||
|
||||
if not (nlhs.is_subnormal() or nrhs.is_subnormal()):
|
||||
# Both of the numbers are normalised
|
||||
return nlhs._data == nrhs._data
|
||||
|
||||
# Either or both of the numbers are subnormal
|
||||
lhs_exp16 = nlhs.exp16
|
||||
rhs_exp16 = nrhs.exp16
|
||||
|
||||
lhs_mantissa = nlhs.int_mantissa
|
||||
rhs_mantissa = nrhs.int_mantissa
|
||||
|
||||
if lhs_exp16 < rhs_exp16:
|
||||
delta_exp16 = rhs_exp16 - lhs_exp16
|
||||
lhs_mantissa >>= 4 * delta_exp16
|
||||
lhs_exp16 += delta_exp16
|
||||
|
||||
if lhs_exp16 > rhs_exp16:
|
||||
delta_exp16 = lhs_exp16 - rhs_exp16
|
||||
rhs_mantissa >>= 4 * delta_exp16
|
||||
rhs_exp16 += delta_exp16
|
||||
|
||||
assert lhs_exp16 == rhs_exp16
|
||||
return lhs_mantissa == rhs_mantissa
|
||||
|
||||
def __floordiv__(self, rhs):
|
||||
return float(self) // float(rhs)
|
||||
|
||||
def __rfloordiv__(self, lhs):
|
||||
return float(lhs) // float(self)
|
||||
|
||||
def __rtruediv__(self, lhs):
|
||||
q = float(lhs) / float(self)
|
||||
return IBMFloat.from_float(q) if isinstance(lhs, float) else q
|
||||
|
||||
def __pow__(self, exponent):
|
||||
p = pow(float(self), float(exponent))
|
||||
return IBMFloat.from_float(p) if isinstance(exponent, IBMFloat) else p
|
||||
|
||||
def __rpow__(self, base):
|
||||
return IBMFloat.from_float(pow(float(base), float(self)))
|
||||
|
||||
def __mod__(self, rhs):
|
||||
m = float(self) % float(rhs)
|
||||
return IBMFloat.from_float(m) if isinstance(rhs, IBMFloat) else m
|
||||
|
||||
def __rmod__(self, lhs):
|
||||
m = float(lhs) % float(self)
|
||||
return IBMFloat.from_float(m) if isinstance(lhs, IBMFloat) else m
|
||||
|
||||
def __rmul__(self, lhs):
|
||||
p = float(lhs) * float(self)
|
||||
return IBMFloat.from_float(p) if isinstance(lhs, IBMFloat) else p
|
||||
|
||||
def __radd__(self, lhs):
|
||||
s = float(lhs) + float(self)
|
||||
return IBMFloat.from_float(s) if isinstance(lhs, IBMFloat) else s
|
||||
|
||||
def __lt__(self, rhs):
|
||||
return float(self) < float(rhs)
|
||||
|
||||
def __le__(self, rhs):
|
||||
return float(self) <= float(rhs)
|
||||
|
||||
def __gt__(self, rhs):
|
||||
return float(self) > float(rhs)
|
||||
|
||||
def __ge__(self, rhs):
|
||||
return float(self) >= float(rhs)
|
||||
|
||||
def __ceil__(self):
|
||||
t = trunc(self)
|
||||
return t if self.signbit else t + 1
|
||||
|
||||
def __floor__(self):
|
||||
t = trunc(self)
|
||||
return t - 1 if self.signbit else t
|
||||
|
||||
@property
|
||||
def exp16(self):
|
||||
"""The base 16 exponent."""
|
||||
exponent_16_biased = self._data[0] & 0x7f
|
||||
exponent_16 = exponent_16_biased - EXPONENT_BIAS
|
||||
return exponent_16
|
||||
|
||||
@property
|
||||
def int_mantissa(self):
|
||||
data = self._data
|
||||
return (data[1] << 16) | (data[2] << 8) | data[3]
|
||||
|
||||
def __trunc__(self):
|
||||
sign = -1 if self.signbit else 1
|
||||
exponent_16 = self.exp16
|
||||
mantissa = self.int_mantissa
|
||||
|
||||
num_nybbles_to_preserve = min(exponent_16, MAX_BITS_PRECISION_IBM_FLOAT // BITS_PER_NYBBLE)
|
||||
num_bits_to_clear = MAX_BITS_PRECISION_IBM_FLOAT - num_nybbles_to_preserve * BITS_PER_NYBBLE
|
||||
clear_mask = 2**num_bits_to_clear - 1
|
||||
preserve_mask = (2**MAX_BITS_PRECISION_IBM_FLOAT - 1) & ~clear_mask
|
||||
|
||||
truncated_mantissa = mantissa & preserve_mask
|
||||
magnitude = truncated_mantissa * pow(16, exponent_16) >> MAX_BITS_PRECISION_IBM_FLOAT
|
||||
return sign * magnitude
|
||||
|
||||
def normalize(self):
|
||||
"""Normalize the floating point value.
|
||||
|
||||
Returns:
|
||||
A normalized IBMFloat equal in value to this object.
|
||||
|
||||
Raises:
|
||||
FloatingPointError: If the number could not be normalized.
|
||||
"""
|
||||
if self.is_zero():
|
||||
return IBM_FLOAT_ZERO
|
||||
|
||||
exponent_16 = self.exp16
|
||||
mantissa = self.int_mantissa
|
||||
|
||||
while mantissa < (1 << 20):
|
||||
new_exponent_16 = exponent_16 - 1
|
||||
if not (-64 <= new_exponent_16 < 64):
|
||||
raise FloatingPointError("Could not normalize {!r} without causing exponent overflow.".format(self))
|
||||
|
||||
mantissa <<= 4
|
||||
exponent_16 = new_exponent_16
|
||||
|
||||
exponent_16_biased = exponent_16 + EXPONENT_BIAS
|
||||
|
||||
sign = int(self.signbit) << 7
|
||||
|
||||
a = sign | exponent_16_biased
|
||||
b = (mantissa >> 16) & 0xff
|
||||
c = (mantissa >> 8) & 0xff
|
||||
d = mantissa & 0xff
|
||||
|
||||
return IBMFloat.from_bytes((a, b, c, d))
|
||||
|
||||
def __round__(self, ndigits=None):
|
||||
return IBMFloat.from_float(round(float(self), ndigits))
|
||||
|
||||
def __truediv__(self, rhs):
|
||||
q = float(self) / float(rhs)
|
||||
return IBMFloat.from_float(q) if isinstance(rhs, IBMFloat) else q
|
||||
|
||||
def __mul__(self, rhs):
|
||||
p = float(self) * float(rhs)
|
||||
return IBMFloat.from_float(p) if isinstance(rhs, IBMFloat) else p
|
||||
|
||||
def __add__(self, rhs):
|
||||
p = float(self) + float(rhs)
|
||||
return IBMFloat.from_float(p) if isinstance(rhs, IBMFloat) else p
|
||||
|
||||
def __int__(self):
|
||||
return trunc(self)
|
||||
|
||||
|
||||
IBM_FLOAT_ZERO = IBMFloat.from_bytes(IBM_ZERO_BYTES)
|
||||
|
||||
|
||||
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
from collections import OrderedDict
|
||||
from struct import Struct
|
||||
from itertools import zip_longest
|
||||
|
||||
from segpy import __version__
|
||||
from segpy.datatypes import SEG_Y_TYPE_TO_CTYPE
|
||||
from segpy.util import pairwise, intervals_partially_overlap, complementary_intervals
|
||||
|
||||
|
||||
def size_of(t):
|
||||
return t.SIZE
|
||||
|
||||
|
||||
def compile_struct(header_format_class, start_offset=0, length_in_bytes=None, endian='>'):
|
||||
"""Compile a struct description from a record.
|
||||
|
||||
Args:
|
||||
header_format_class: A header_format class.
|
||||
|
||||
start_offset: Optional start offset for the header in bytes. Indicates the position of the start of
|
||||
the header in the same reference frame as which the field offsets are given.
|
||||
|
||||
length_in_bytes: Optional length in bytes for the header. If the supplied header described a format shorter
|
||||
than this value the returned format will be padded with placeholders for bytes to be discarded. If the
|
||||
value is less than the minimum required for the format described by header_format_class an error will be
|
||||
raised.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard).
|
||||
|
||||
Returns:
|
||||
A two-tuple containing in the zeroth element a format string which can be used with the struct.unpack function,
|
||||
and in the second element containing a list-of-lists for field names. Each item in the outer list corresponds
|
||||
to an element of the tuple of data values returned by struct.unpack(); each name associated with that index is a
|
||||
field to which the unpacked value should be assigned.
|
||||
|
||||
format, allocations = compile_struct(TraceHeaderFormat)
|
||||
values = struct.unpack(format)
|
||||
field_names_to_values = {}
|
||||
for field_names, value in zip(allocations, values):
|
||||
for field_name in field_names:
|
||||
field_names_to_values[field_name] = value
|
||||
header = Header(**field_names_to_values)
|
||||
|
||||
Raises:
|
||||
ValueError: If header_format_class defines no fields.
|
||||
ValueError: If header_format_class contains fields which overlap but are not exactly coincident.
|
||||
ValueError: If header_format_class contains coincident fields of different types.
|
||||
ValueError: If header_format_class described a format longer than length_in_bytes.
|
||||
|
||||
"""
|
||||
fields = [getattr(header_format_class, name) for name in header_format_class.ordered_field_names()]
|
||||
|
||||
sorted_fields = sorted(fields, key=lambda f: f.offset)
|
||||
|
||||
if len(sorted_fields) < 1:
|
||||
raise ValueError("Header format class {!r} defines no fields".format(header_format_class.__name__))
|
||||
|
||||
if len(sorted_fields) > 1:
|
||||
for a, b in pairwise(sorted_fields):
|
||||
if intervals_partially_overlap(range(a.offset, a.offset + size_of(a.value_type)),
|
||||
range(b.offset, b.offset + size_of(b.value_type))):
|
||||
raise ValueError("Record fields {!r} at offset {} and {!r} at offset {} are distinct but overlap."
|
||||
.format(a.name, a.offset, b.name, b.offset))
|
||||
|
||||
last_field = sorted_fields[-1]
|
||||
defined_length = (last_field.offset - start_offset) + size_of(last_field.value_type)
|
||||
specified_length = defined_length if (length_in_bytes is None) else length_in_bytes
|
||||
padding_length = specified_length - defined_length
|
||||
if padding_length < 0:
|
||||
raise ValueError("Header length {!r} bytes defined by {!r} is less than specified length in bytes {!r}"
|
||||
.format(defined_length, header_format_class.__name__, specified_length))
|
||||
|
||||
offset_to_fields = OrderedDict()
|
||||
for field in sorted_fields:
|
||||
relative_offset = field.offset - start_offset # relative_offser is zero-based
|
||||
if relative_offset not in offset_to_fields:
|
||||
offset_to_fields[relative_offset] = []
|
||||
if len(offset_to_fields[relative_offset]) > 0:
|
||||
if offset_to_fields[relative_offset][0].value_type is not field.value_type:
|
||||
# TODO: Test this error handling
|
||||
raise ValueError("Coincident fields {!r} and {!r} at offset {} have different types {!r} and {!r}"
|
||||
.format(offset_to_fields[relative_offset][0],
|
||||
field,
|
||||
offset_to_fields[relative_offset][0].offset,
|
||||
offset_to_fields[relative_offset][0].value_type,
|
||||
field.value_type))
|
||||
offset_to_fields[relative_offset].append(field)
|
||||
|
||||
# Create a list of ranges where each range spans the byte indexes covered by each field
|
||||
field_spans = [range(offset, offset + size_of(fields[0].value_type))
|
||||
for offset, fields in offset_to_fields.items()]
|
||||
|
||||
gap_intervals = complementary_intervals(field_spans, start=0, stop=specified_length) # One-based indexes
|
||||
|
||||
# Create a format string usable with the struct module
|
||||
format_chunks = [endian]
|
||||
representative_fields = (fields[0] for fields in offset_to_fields.values())
|
||||
for gap_interval, field in zip_longest(gap_intervals, representative_fields, fillvalue=None):
|
||||
gap_length = len(gap_interval)
|
||||
if gap_length > 0:
|
||||
format_chunks.append('x' * gap_length)
|
||||
if field is not None:
|
||||
format_chunks.append(SEG_Y_TYPE_TO_CTYPE[field.value_type.SEG_Y_TYPE])
|
||||
cformat = ''.join(format_chunks)
|
||||
|
||||
# Create a list of mapping item index to field names.
|
||||
# [0] -> ['field_1', 'field_2']
|
||||
# [1] -> ['field_3']
|
||||
# [2] -> ['field_4']
|
||||
field_name_allocations = [[field.name for field in fields]
|
||||
for fields in offset_to_fields.values()]
|
||||
return cformat, field_name_allocations
|
||||
|
||||
|
||||
def make_header_packer(header_format_class, endian='>'):
|
||||
cformat, field_name_allocations = compile_struct(
|
||||
header_format_class,
|
||||
header_format_class.START_OFFSET_IN_BYTES,
|
||||
header_format_class.LENGTH_IN_BYTES,
|
||||
endian)
|
||||
structure = Struct(cformat)
|
||||
|
||||
one_to_one = all(len(fields) == 1 for fields in field_name_allocations)
|
||||
|
||||
if one_to_one:
|
||||
return BijectiveHeaderPacker(header_format_class, structure, field_name_allocations)
|
||||
return SurjectiveHeaderPacker(header_format_class, structure, field_name_allocations)
|
||||
|
||||
|
||||
class HeaderPacker:
|
||||
"""Packing and unpacking header instances."""
|
||||
|
||||
def __init__(self, header_format_class, structure, field_name_allocations):
|
||||
self._header_format_class = header_format_class
|
||||
self._structure = structure
|
||||
self._field_name_allocations = field_name_allocations
|
||||
|
||||
def __getstate__(self):
|
||||
|
||||
state = self.__dict__.copy()
|
||||
state['__version__'] = __version__
|
||||
state['_structure_format'] = self._structure.format
|
||||
del state['_structure']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
if state['__version__'] != __version__:
|
||||
raise TypeError("Cannot unpickle {} version {} into version {}"
|
||||
.format(self.__class__.__name__,
|
||||
state['__version__'],
|
||||
__version__))
|
||||
del state['__version__']
|
||||
|
||||
structure = Struct(state['_structure_format'])
|
||||
state['_structure'] = structure
|
||||
del state['_structure_format']
|
||||
self.__dict__.update(state)
|
||||
|
||||
@property
|
||||
def header_format_class(self):
|
||||
return self._header_format_class
|
||||
|
||||
def pack(self, header):
|
||||
"""Pack a header into a buffer.
|
||||
"""
|
||||
if not isinstance(header, self._header_format_class):
|
||||
raise TypeError("{}({}) cannot pack header of type {}.".format(
|
||||
self.__class__.__name__,
|
||||
self._header_format_class.__name__,
|
||||
header.__class__.__name__
|
||||
))
|
||||
values = [getattr(header, names[0]) for names in self._field_name_allocations]
|
||||
return self._structure.pack(*values)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({})".format(
|
||||
self.__class__.__name__,
|
||||
self._header_format_class.__name__)
|
||||
|
||||
|
||||
class BijectiveHeaderPacker(HeaderPacker):
|
||||
"""One-to-one packing/unpacking of serialised values to header fields."""
|
||||
|
||||
def unpack(self, buffer):
|
||||
"""Unpack a header into a header object.
|
||||
|
||||
Overwrites any existing header field values with new values
|
||||
obtained from the buffer.
|
||||
|
||||
Returns:
|
||||
The header object.
|
||||
"""
|
||||
values = self._structure.unpack(buffer)
|
||||
return self._header_format_class(*values)
|
||||
|
||||
|
||||
class SurjectiveHeaderPacker(HeaderPacker):
|
||||
"""One-to-many unpacking of serialised values to header fields."""
|
||||
|
||||
def unpack(self, buffer):
|
||||
"""Unpack a header into a header object.
|
||||
|
||||
Overwrites any existing header field values with new values
|
||||
obtained from the buffer.
|
||||
|
||||
Returns:
|
||||
The header object.
|
||||
"""
|
||||
values = self._structure.unpack(buffer)
|
||||
|
||||
kwargs = {name: value
|
||||
for names, value in zip(self._field_name_allocations, values)
|
||||
for name in names}
|
||||
|
||||
return self._header_format_class(**kwargs)
|
||||
|
||||
|
||||
def main():
|
||||
from segpy.trace_header import TraceHeaderRev0
|
||||
compile_struct(TraceHeaderRev0, 1, 240)
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+867
@@ -0,0 +1,867 @@
|
||||
import os
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
|
||||
from segpy import __version__
|
||||
from segpy.encoding import ASCII
|
||||
from segpy.packer import make_header_packer
|
||||
from segpy.trace_header import TraceHeaderRev1
|
||||
from segpy.util import file_length, filename_from_handle, make_sorted_distinct_sequence, hash_for_file, UNKNOWN_FILENAME
|
||||
from segpy.datatypes import DATA_SAMPLE_FORMAT_TO_SEG_Y_TYPE, SEG_Y_TYPE_DESCRIPTION, SEG_Y_TYPE_TO_CTYPE, size_in_bytes
|
||||
from segpy.toolkit import (extract_revision,
|
||||
bytes_per_sample,
|
||||
read_binary_reel_header,
|
||||
read_trace_header,
|
||||
catalog_traces,
|
||||
catalog_fixed_length_traces,
|
||||
read_binary_values,
|
||||
REEL_HEADER_NUM_BYTES,
|
||||
TRACE_HEADER_NUM_BYTES,
|
||||
read_textual_reel_header,
|
||||
read_extended_textual_headers,
|
||||
guess_textual_header_encoding)
|
||||
|
||||
|
||||
def create_reader(fh, encoding=None, trace_header_format=TraceHeaderRev1, endian='>', progress=None, cache_directory=".segpy", fast=False):
|
||||
"""Create a SegYReader (or one of its subclasses) based on performing
|
||||
a scan of SEG Y data.
|
||||
|
||||
This function is the preferred method for creating SegYReader
|
||||
objects. It reads basic header information and attempts to build
|
||||
indexes for traces, CDP numbers (for 2D surveys), and inline and
|
||||
cross line co-ordinates (for 3D surveys) to facilitate subsequent
|
||||
random-access to traces.
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open in binary mode positioned such
|
||||
that the beginning of the reel header will be the next
|
||||
byte to be read. For disk-based SEG Y files, this is the
|
||||
beginning of the file.
|
||||
|
||||
encoding: An optional text encoding for the textual headers. If
|
||||
None (the default) a heuristic will be used to guess the
|
||||
header encoding.
|
||||
|
||||
trace_header_format: An optional class defining the layout of the
|
||||
trace header. Defaults to TraceHeaderRev1.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
|
||||
progress: A unary callable which will be passed a number
|
||||
between zero and one indicating the progress made. If
|
||||
provided, this callback will be invoked at least once with
|
||||
an argument equal to one.
|
||||
|
||||
cache_directory: The directory for the cache file. Relative paths
|
||||
are interpreted as being relative to the directory containing
|
||||
the SEG Y file. Absolute paths are used as is. If
|
||||
cache_directory is None, caching is disabled.
|
||||
|
||||
fast: Boolean flag to try a quick fixed length catalog before inline or
|
||||
CDP catalogs.
|
||||
|
||||
Raises:
|
||||
ValueError: ``fh`` is unsuitable for some reason, such as not
|
||||
being open, not being seekable, not being in
|
||||
binary mode, or being too short.
|
||||
|
||||
Returns:
|
||||
A SegYReader object. Depending on the exact type of the
|
||||
SegYReader returned different capabilities may be
|
||||
available. Inspect the returned object to determine these
|
||||
capabilities, or be prepared for capabilities not defined in
|
||||
the SegYReader base class to be unavailable. The underlying
|
||||
file-like object must remain open for the duration of use of
|
||||
the returned reader object. It is the caller's responsibility
|
||||
to close the underlying file.
|
||||
|
||||
Example:
|
||||
|
||||
with open('my_seismic_data.sgy', 'rb') as fh:
|
||||
reader = create_reader(fh)
|
||||
print(reader.num_traces())
|
||||
|
||||
"""
|
||||
if hasattr(fh, 'encoding') and fh.encoding is not None:
|
||||
raise TypeError(
|
||||
"SegYReader must be provided with a binary mode file object")
|
||||
|
||||
if not fh.seekable():
|
||||
raise TypeError(
|
||||
"SegYReader must be provided with a seekable file object")
|
||||
|
||||
if fh.closed:
|
||||
raise ValueError(
|
||||
"SegYReader must be provided with an open file object")
|
||||
|
||||
num_file_bytes = file_length(fh)
|
||||
if num_file_bytes < REEL_HEADER_NUM_BYTES:
|
||||
raise ValueError(
|
||||
"SEG Y file {!r} of {} bytes is too short".format(
|
||||
filename_from_handle(fh),
|
||||
num_file_bytes))
|
||||
|
||||
if endian not in ('<', '>'):
|
||||
raise ValueError("Unrecognised endian value {!r}".format(endian))
|
||||
|
||||
reader = None
|
||||
cache_file_path = None
|
||||
|
||||
if cache_directory is not None:
|
||||
sha1 = hash_for_file(fh, encoding, trace_header_format, endian)
|
||||
seg_y_path = filename_from_handle(fh)
|
||||
cache_file_path = _locate_cache_file(seg_y_path, cache_directory, sha1)
|
||||
if cache_file_path is not None:
|
||||
reader = _load_reader_from_cache(cache_file_path, seg_y_path)
|
||||
|
||||
if reader is None:
|
||||
reader = _make_reader(fh, encoding, trace_header_format, endian, progress, fast=fast)
|
||||
if cache_directory is not None:
|
||||
_save_reader_to_cache(reader, cache_file_path)
|
||||
|
||||
return reader
|
||||
|
||||
|
||||
def _locate_cache_file(seg_y_path, cache_directory, sha1):
|
||||
"""Determine the location of the cache file.
|
||||
|
||||
Args:
|
||||
seg_y_path: The path to the SEG Y file.
|
||||
|
||||
cache_directory: The directory for the cache file. Relative paths
|
||||
are interpreted as being relative to the directory containing
|
||||
the SEG Y file. Absolute paths are used as is.
|
||||
|
||||
sha1: The SHA1 hash corresponding to the file.
|
||||
|
||||
Returns:
|
||||
A Path object containing the absolute path of the cache file or None
|
||||
if the cache file path could not be determined.
|
||||
"""
|
||||
cache_dir_path = Path(cache_directory)
|
||||
cache_filename = (sha1 + '.p')
|
||||
if cache_dir_path.is_absolute():
|
||||
cache_file_path = cache_dir_path / cache_filename
|
||||
else:
|
||||
if seg_y_path != UNKNOWN_FILENAME:
|
||||
normalized_seg_y_path = Path(seg_y_path).resolve()
|
||||
cache_file_path = normalized_seg_y_path.parent / cache_directory / cache_filename
|
||||
else:
|
||||
cache_file_path = None
|
||||
return cache_file_path
|
||||
|
||||
|
||||
def _save_reader_to_cache(reader, cache_file_path):
|
||||
"""Save a reader object to a pickle file.
|
||||
|
||||
Args:
|
||||
reader: The Reader instance to be persisted.
|
||||
cache_file_path: A Path instance giving the path to the pickle file location.
|
||||
"""
|
||||
cache_path = cache_file_path.parent
|
||||
os.makedirs(str(cache_path), exist_ok=True)
|
||||
try:
|
||||
with cache_file_path.open('wb') as cache_file:
|
||||
try:
|
||||
pickle.dump(reader, cache_file)
|
||||
except (pickle.PicklingError, TypeError) as pickling_error:
|
||||
print("Could not pickle {} because {}".format(reader, pickling_error))
|
||||
pass
|
||||
except OSError as os_error:
|
||||
print("Could not cache {} because {}".format(reader, os_error))
|
||||
|
||||
|
||||
def _load_reader_from_cache(cache_file_path, seg_y_path):
|
||||
"""Attempt to load a reader object from cache.
|
||||
|
||||
Any cache file could be located but not successfully read is removed.
|
||||
|
||||
Args:
|
||||
cache_file_path: A Path object referring to the pickle file.
|
||||
|
||||
seg_y_filename: The name of the SEG Y file which the pickled reader is expected
|
||||
to be able to read (used for error reporting).
|
||||
|
||||
Returns:
|
||||
A Reader instance associated with seg_y_filename.
|
||||
|
||||
Raises:
|
||||
TypeError: If the pickle could be read, but did not contain a SegYReader.
|
||||
"""
|
||||
|
||||
if not (cache_file_path.exists() and cache_file_path.is_file()):
|
||||
return None
|
||||
|
||||
with cache_file_path.open('rb') as pickle_file:
|
||||
try:
|
||||
reader = pickle.load(pickle_file)
|
||||
except (pickle.UnpicklingError, TypeError, EOFError) as unpickling_error:
|
||||
reader = None
|
||||
print("Could not unpickle reader for {} because {}".format(seg_y_path, unpickling_error))
|
||||
try:
|
||||
cache_file_path.unlink()
|
||||
except OSError as os_error:
|
||||
print("Could not remove stale cache entry {} for {} because {}"
|
||||
.format(cache_file_path, seg_y_path, os_error))
|
||||
else:
|
||||
print("Removed stale cache entry {} for {}".format(cache_file_path, seg_y_path))
|
||||
if not isinstance(reader, SegYReader):
|
||||
raise TypeError("Pickle at {} does not contain a {} instance.".format(cache_file_path, SegYReader.__name__))
|
||||
return reader
|
||||
|
||||
|
||||
def _make_reader(fh, encoding, trace_header_format, endian, progress, fast=False):
|
||||
if encoding is None:
|
||||
encoding = guess_textual_header_encoding(fh)
|
||||
if encoding is None:
|
||||
encoding = ASCII
|
||||
textual_reel_header = read_textual_reel_header(fh, encoding)
|
||||
binary_reel_header = read_binary_reel_header(fh, endian)
|
||||
extended_textual_header = read_extended_textual_headers(fh, binary_reel_header, encoding)
|
||||
revision = extract_revision(binary_reel_header)
|
||||
bps = bytes_per_sample(binary_reel_header, revision)
|
||||
if fast:
|
||||
try:
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_fixed_length_traces(fh, binary_reel_header, trace_header_format,endian, progress)
|
||||
except:
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_traces(fh, bps, trace_header_format,endian, progress)
|
||||
else:
|
||||
try:
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_traces(fh, bps, trace_header_format,endian, progress)
|
||||
except:
|
||||
fh.seek(REEL_HEADER_NUM_BYTES)
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_fixed_length_traces(fh, binary_reel_header, trace_header_format,endian, progress)
|
||||
|
||||
|
||||
if line_catalog is not None:
|
||||
return SegYReader3D(fh, textual_reel_header, binary_reel_header, extended_textual_header, trace_offset_catalog,
|
||||
trace_length_catalog, line_catalog, trace_header_format, encoding, endian)
|
||||
if cdp_catalog is not None:
|
||||
return SegYReader2D(fh, textual_reel_header, binary_reel_header, extended_textual_header, trace_offset_catalog,
|
||||
trace_length_catalog, cdp_catalog, trace_header_format, encoding, endian)
|
||||
|
||||
return SegYReader(fh, textual_reel_header, binary_reel_header, extended_textual_header, trace_offset_catalog,
|
||||
trace_length_catalog, trace_header_format, encoding, endian)
|
||||
|
||||
|
||||
class SegYReader(object):
|
||||
"""A basic SEG Y reader.
|
||||
|
||||
Use to obtain read the reel header, the trace_samples headers or trace_samples
|
||||
values. Traces can be accessed only by trace_samples index.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
fh,
|
||||
textual_reel_header,
|
||||
binary_reel_header,
|
||||
extended_textual_headers,
|
||||
trace_offset_catalog,
|
||||
trace_length_catalog,
|
||||
trace_header_format,
|
||||
encoding,
|
||||
endian='>'):
|
||||
"""Initialize a SegYReader around a file-like-object.
|
||||
|
||||
Note:
|
||||
Usually a SegYReader is most easily constructed using the
|
||||
create_reader() function.
|
||||
|
||||
Args:
|
||||
fh: A file-like object, which must support seeking and
|
||||
support binary reading.
|
||||
|
||||
textual_reel_header: A sequence of forty 80-character Unicode strings
|
||||
containing header data.
|
||||
|
||||
binary_reel_header: A Header object containing reel header data.
|
||||
|
||||
extended_textual_headers: A sequence of sequences of Unicode strings.
|
||||
|
||||
trace_offset_catalog: A mapping from zero-based trace_samples index to
|
||||
the byte-offset to individual traces within the file.
|
||||
|
||||
trace_length_catalog: A mapping from zero-based trace_samples index to the
|
||||
number of samples in that trace_samples.
|
||||
|
||||
trace_header_format: The class defining the layout of the trace header.
|
||||
|
||||
encoding: Either ASCII or EBCDIC.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<' for
|
||||
little-endian (non-standard)
|
||||
|
||||
"""
|
||||
self._fh = fh
|
||||
self._endian = endian
|
||||
self._encoding = encoding
|
||||
|
||||
self._textual_reel_header = textual_reel_header
|
||||
self._binary_reel_header = binary_reel_header
|
||||
self._extended_textual_headers = extended_textual_headers
|
||||
|
||||
self._trace_header_packer = make_header_packer(trace_header_format, endian)
|
||||
|
||||
self._trace_offset_catalog = trace_offset_catalog
|
||||
self._trace_length_catalog = trace_length_catalog
|
||||
|
||||
self._revision = extract_revision(self._binary_reel_header)
|
||||
self._bytes_per_sample = bytes_per_sample(
|
||||
self._binary_reel_header, self.revision)
|
||||
self._max_num_trace_samples = None
|
||||
|
||||
def __getstate__(self):
|
||||
"""Copy the reader's state to a pickleable dictionary.
|
||||
|
||||
Note:
|
||||
Subclasses which have non-pickleable attributes must
|
||||
override this method.
|
||||
"""
|
||||
if self._fh.closed:
|
||||
raise TypeError("Cannot pickle {} object where file handle has been closed"
|
||||
.format(self.__class__.__name__))
|
||||
|
||||
filename = filename_from_handle(self._fh)
|
||||
if filename == '<unknown>':
|
||||
raise TypeError("Cannot pickle {} object where file handle has filename {!r}"
|
||||
.format(self.__class__.__name__), filename)
|
||||
file_pos = self._fh.tell()
|
||||
file_mode = self._fh.mode
|
||||
|
||||
_ = self.max_num_trace_samples()
|
||||
|
||||
state = self.__dict__.copy()
|
||||
state['__version__'] = __version__
|
||||
state['_file_name'] = filename
|
||||
state['_file_pos'] = file_pos
|
||||
state['_file_mode'] = file_mode
|
||||
del state['_fh']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""Restore the reader's state from an unpickled dictionary.
|
||||
|
||||
Note: Subclasses which have non-pickleable attributes must
|
||||
override this method.
|
||||
"""
|
||||
if state['__version__'] != __version__:
|
||||
raise TypeError("Cannot unpickle {} version {} into version {}"
|
||||
.format(self.__class__.__name__,
|
||||
state['__version__'],
|
||||
__version__))
|
||||
del state['__version__']
|
||||
|
||||
try:
|
||||
fh = open(state['_file_name'], state['_file_mode'])
|
||||
except OSError as e:
|
||||
raise TypeError("Cannot unpickle {} because file {} could not be opened"
|
||||
.format(self.__class__.__name__, state['_filename']))
|
||||
else:
|
||||
self._fh = fh
|
||||
del state['_file_name']
|
||||
del state['_file_mode']
|
||||
|
||||
file_pos = state['_file_pos']
|
||||
fh.seek(file_pos)
|
||||
del state['_file_pos']
|
||||
|
||||
self.__dict__.update(state)
|
||||
|
||||
def trace_indexes(self):
|
||||
"""An iterator over zero-based trace_samples indexes.
|
||||
|
||||
Returns:
|
||||
An iterator which yields integers in the range zero to
|
||||
num_traces() - 1
|
||||
"""
|
||||
return iter(self._trace_offset_catalog)
|
||||
|
||||
def num_traces(self):
|
||||
"""The number of traces"""
|
||||
return len(self._trace_offset_catalog)
|
||||
|
||||
def max_num_trace_samples(self):
|
||||
"""The number of samples in the trace_samples with the most samples."""
|
||||
if self._max_num_trace_samples is None:
|
||||
self._max_num_trace_samples = max(self._trace_length_catalog.values())
|
||||
return self._max_num_trace_samples
|
||||
|
||||
def num_trace_samples(self, trace_index):
|
||||
"""The number of samples in the specified trace_samples."""
|
||||
return self._trace_length_catalog[trace_index]
|
||||
|
||||
def trace_samples(self, trace_index, start=None, stop=None):
|
||||
"""Read a specific trace_samples.
|
||||
|
||||
Args:
|
||||
trace_index: An integer in the range zero to num_traces() - 1
|
||||
|
||||
start: Optional zero-based start sample index. The default
|
||||
is to read from the first (i.e. zeroth) sample.
|
||||
|
||||
stop: Optional zero-based stop sample index. Following Python
|
||||
slice convention this is one beyond the end.
|
||||
|
||||
Returns:
|
||||
A sequence of numeric trace_samples samples.
|
||||
|
||||
Example:
|
||||
|
||||
first_trace_samples = segy_reader.trace_samples(0)
|
||||
part_of_second_trace_samples = segy_reader.trace_samples(1, 1000, 2000)
|
||||
"""
|
||||
if not (0 <= trace_index < self.num_traces()):
|
||||
raise ValueError("Trace index out of range.")
|
||||
|
||||
num_samples_in_trace = self.num_trace_samples(trace_index)
|
||||
|
||||
start_sample = start if start is not None else 0
|
||||
stop_sample = stop if stop is not None else num_samples_in_trace
|
||||
|
||||
if not (0 <= stop_sample <= num_samples_in_trace):
|
||||
raise ValueError("trace_samples(): stop value {} out of range 0 to {}"
|
||||
.format(stop, num_samples_in_trace))
|
||||
|
||||
if not (0 <= start_sample <= stop_sample):
|
||||
raise ValueError("trace_samples(): start value {} out of range 0 to {}"
|
||||
.format(start, stop_sample))
|
||||
|
||||
dsf = self._binary_reel_header.data_sample_format
|
||||
seg_y_type = DATA_SAMPLE_FORMAT_TO_SEG_Y_TYPE[dsf]
|
||||
start_pos = (self._trace_offset_catalog[trace_index]
|
||||
+ TRACE_HEADER_NUM_BYTES
|
||||
+ start_sample * size_in_bytes(SEG_Y_TYPE_TO_CTYPE[seg_y_type]))
|
||||
num_samples_to_read = stop_sample - start_sample
|
||||
|
||||
trace_values = read_binary_values(
|
||||
self._fh, start_pos, seg_y_type, num_samples_to_read, self._endian)
|
||||
return trace_values
|
||||
|
||||
def trace_header(self, trace_index, header_packer_override=None):
|
||||
"""Read a specific trace_samples.
|
||||
|
||||
Args:
|
||||
trace_index: An integer in the range zero to num_traces() - 1
|
||||
|
||||
header_packer_override: Override the default header packer (for example
|
||||
to more efficiently extract only a few fields)
|
||||
|
||||
Returns:
|
||||
A TraceHeader corresponding to the requested trace_samples.
|
||||
|
||||
Example:
|
||||
|
||||
first_trace_header, first_trace_samples = segy_reader.trace_samples(0)
|
||||
"""
|
||||
if not (0 <= trace_index < self.num_traces()):
|
||||
raise ValueError("Trace index {} out of range".format(trace_index))
|
||||
header_packer = self._trace_header_packer if header_packer_override is None else header_packer_override
|
||||
pos = self._trace_offset_catalog[trace_index]
|
||||
trace_header = read_trace_header(self._fh, header_packer, pos)
|
||||
return trace_header
|
||||
|
||||
@property
|
||||
def trace_header_format_class(self):
|
||||
"""The trace header format class.
|
||||
|
||||
Instances of this class are what is returned from trace_header() unless the
|
||||
header_packer has been overridden."""
|
||||
return self._trace_header_packer.header_format_class
|
||||
|
||||
@property
|
||||
def dimensionality(self):
|
||||
"""The spatial dimensionality of the data.
|
||||
|
||||
Returns:
|
||||
3 for 3D seismic volumes, 2 for 2D seismic lines, 1 for a
|
||||
single trace_samples, otherwise 0.
|
||||
|
||||
"""
|
||||
return self._dimensionality()
|
||||
|
||||
def _dimensionality(self):
|
||||
return 1 if self.num_traces() == 1 else 0
|
||||
|
||||
@property
|
||||
def textual_reel_header(self):
|
||||
"""The textual real header.
|
||||
|
||||
An immutable sequence of forty Unicode strings each 80 characters long.
|
||||
"""
|
||||
return self._textual_reel_header
|
||||
|
||||
@property
|
||||
def binary_reel_header(self):
|
||||
"""The binary reel header.
|
||||
|
||||
A dictionary containing data from the reel header.
|
||||
"""
|
||||
return self._binary_reel_header
|
||||
|
||||
@property
|
||||
def extended_textual_header(self):
|
||||
"""A sequence of sequences of Unicode strings.
|
||||
|
||||
If there were no headers, the sequence will be empty.
|
||||
"""
|
||||
return self._extended_textual_headers
|
||||
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
"""The filename.
|
||||
|
||||
Returns:
|
||||
The filename if it could be determined, otherwise '<unknown>'
|
||||
"""
|
||||
return filename_from_handle(self._fh)
|
||||
|
||||
@property
|
||||
def revision(self):
|
||||
"""The SEG Y revision.
|
||||
|
||||
Returns:
|
||||
Either datatypes.SEGY_REVISION_0 or datatypes.SEGY_REVISION_1
|
||||
"""
|
||||
return self._revision
|
||||
|
||||
@property
|
||||
def bytes_per_sample(self):
|
||||
"""The number of bytes per trace_samples sample.
|
||||
"""
|
||||
return self._bytes_per_sample
|
||||
|
||||
@property
|
||||
def data_sample_format(self):
|
||||
"""The data type of the samples in machine-readable form.
|
||||
|
||||
Returns:
|
||||
One of the values from datatypes.DATA_SAMPLE_FORMAT
|
||||
"""
|
||||
return DATA_SAMPLE_FORMAT_TO_SEG_Y_TYPE[self._binary_reel_header.data_sample_format]
|
||||
|
||||
@property
|
||||
def data_sample_format_description(self):
|
||||
"""A descriptive human-readable description of the data sample format
|
||||
"""
|
||||
return SEG_Y_TYPE_DESCRIPTION[self.data_sample_format]
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
"""The encoding, of the data in the underlying file. Either ASCII ('ascii'),
|
||||
EBCDIC ('cp037') or None."""
|
||||
return self._encoding
|
||||
|
||||
@property
|
||||
def endian(self):
|
||||
"""The endianness of the data in the underlying file. Either '>' for big-endian or '<' for
|
||||
little endian or None."""
|
||||
return self._endian
|
||||
|
||||
|
||||
class SegYReader3D(SegYReader):
|
||||
"""A reader for 3D seismic data.
|
||||
|
||||
In addition to the capabilities provided by the SegYReader base
|
||||
class, this reader provides an index to facilitate random access
|
||||
to individual traces via crossline and inline co-ordinates.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
fh,
|
||||
textual_reel_header,
|
||||
binary_reel_header,
|
||||
extended_textual_headers,
|
||||
trace_offset_catalog,
|
||||
trace_length_catalog,
|
||||
line_catalog,
|
||||
trace_header_format,
|
||||
encoding,
|
||||
endian='>'):
|
||||
"""Initialize a SegYReader3D around a file-like-object.
|
||||
|
||||
Note:
|
||||
Usually a SegYReader is most easily constructed using the
|
||||
create_reader() function.
|
||||
|
||||
Args:
|
||||
fh: A file-like object, which must support seeking and
|
||||
support binary reading.
|
||||
|
||||
binary_reel_header: A dictionary containing reel header data.
|
||||
|
||||
trace_offset_catalog: A mapping from zero-based trace_samples indexes to
|
||||
the byte-offset to individual traces within the file.
|
||||
|
||||
trace_length_catalog: A mapping from zero-based trace_samples indexes to
|
||||
the number of samples in that trace_samples.
|
||||
|
||||
line_catalog: A mapping from (xline, inline) tuples to
|
||||
trace_indexes.
|
||||
|
||||
trace_header_format: The class defining the layout of the trace header.
|
||||
|
||||
encoding: Either ASCII or EBCDIC.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<' for
|
||||
little-endian (non-standard)
|
||||
"""
|
||||
super(SegYReader3D, self).__init__(fh, textual_reel_header, binary_reel_header, extended_textual_headers,
|
||||
trace_offset_catalog, trace_length_catalog, trace_header_format,
|
||||
encoding, endian)
|
||||
self._line_catalog = line_catalog
|
||||
self._inline_numbers = None
|
||||
self._xline_numbers = None
|
||||
|
||||
def __getstate__(self):
|
||||
# As we're pickling, force evaluation of these properties so they'll be cached
|
||||
_ = self.inline_numbers()
|
||||
_ = self.xline_numbers()
|
||||
state = super().__getstate__()
|
||||
return state
|
||||
|
||||
def _dimensionality(self):
|
||||
return 3
|
||||
|
||||
def inline_numbers(self):
|
||||
"""A sorted immutable collection of inline numbers.
|
||||
|
||||
Test for membership in this collection to determine if a particular inline
|
||||
exists or iterate over this collection to generate all inline numbers in
|
||||
order.
|
||||
|
||||
Returns:
|
||||
A sorted immutable collection of inline numbers which supports the
|
||||
Sized, Iterable, Container and Sequence protocols.
|
||||
"""
|
||||
if self._inline_numbers is None:
|
||||
if hasattr(self._line_catalog, 'i_range'):
|
||||
self._inline_numbers = self._line_catalog.i_range
|
||||
else:
|
||||
self._inline_numbers = make_sorted_distinct_sequence(i for i, j in self._line_catalog)
|
||||
return self._inline_numbers
|
||||
|
||||
def num_inlines(self):
|
||||
"""The number of distinct inlines in the survey."""
|
||||
return len(self.inline_numbers())
|
||||
|
||||
def xline_numbers(self):
|
||||
"""A sorted immutable collection of crossline numbers.
|
||||
|
||||
Test for membership in this collection to determine if a particular crossline
|
||||
exists or iterate over this collection to generate all crossline numbers in
|
||||
order.
|
||||
|
||||
Returns:
|
||||
A sorted immutable collection of crossline numbers which supports the
|
||||
Sized, Iterable, Container and Sequence protocols.
|
||||
"""
|
||||
if self._xline_numbers is None:
|
||||
if hasattr(self._line_catalog, 'j_range'):
|
||||
self._xline_numbers = self._line_catalog.j_range
|
||||
else:
|
||||
self._xline_numbers = make_sorted_distinct_sequence(j for i, j in self._line_catalog)
|
||||
return self._xline_numbers
|
||||
|
||||
def num_xlines(self):
|
||||
"""The number of distinct crosslines in the survey."""
|
||||
return len(self.xline_numbers())
|
||||
|
||||
|
||||
def inline_xline_numbers(self):
|
||||
"""An iterator over all (inline_number, xline_number) tuples
|
||||
corresponding to traces.
|
||||
"""
|
||||
return iter(self._line_catalog)
|
||||
|
||||
def has_trace_index(self, inline_xline):
|
||||
"""Determine whether a specific trace_samples exists.
|
||||
|
||||
Args:
|
||||
inline_xline: A 2-tuple of inline number, crossline number.
|
||||
|
||||
Returns:
|
||||
True if the specified trace_samples exists, otherwise False.
|
||||
"""
|
||||
return inline_xline in self._line_catalog
|
||||
|
||||
def trace_index(self, inline_xline):
|
||||
"""Obtain the trace_samples index given an xline and a inline.
|
||||
|
||||
Note:
|
||||
Do not assume that all combinations of crossline and
|
||||
inline co-ordinates are valid. The volume may not be
|
||||
rectangular. Valid values can be obtained from the
|
||||
inline_xline_numbers() iterator.
|
||||
|
||||
Furthermore, inline and crossline numbers should not be
|
||||
relied upon to be zero- or one-based indexes (although
|
||||
they may be).
|
||||
|
||||
Args:
|
||||
inline_xline: A 2-tuple of inline number, crossline number.
|
||||
|
||||
Returns:
|
||||
A trace_samples index which can be used with trace_samples().
|
||||
"""
|
||||
return self._line_catalog[inline_xline]
|
||||
|
||||
|
||||
class SegYReader2D(SegYReader):
|
||||
def __init__(self,
|
||||
fh,
|
||||
textual_reel_header,
|
||||
binary_reel_header,
|
||||
extended_textual_headers,
|
||||
trace_offset_catalog,
|
||||
trace_length_catalog,
|
||||
cdp_catalog,
|
||||
trace_header_format,
|
||||
encoding,
|
||||
endian='>'):
|
||||
"""Initialize a SegYReader2D around a file-like-object.
|
||||
|
||||
Note:
|
||||
Usually a SegYReader is most easily constructed using the
|
||||
create_reader() function.
|
||||
|
||||
Args:
|
||||
fh: A file-like object, which must support seeking and
|
||||
support binary reading.
|
||||
|
||||
binary_reel_header: A dictionary containing reel header data.
|
||||
|
||||
trace_catalog_offset: A mapping from zero-based trace_samples index to
|
||||
the byte-offset to individual traces within the file.
|
||||
|
||||
trace_length_catalog: A mapping from zero-based trace_samples indexes to
|
||||
the number of samples in that trace_samples.
|
||||
|
||||
cdp_catalog: A mapping from CDP numbers to trace_indexes.
|
||||
|
||||
trace_header_format: The class defining the layout of the trace header.
|
||||
|
||||
encoding: Either ASCII or EBCDIC.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<' for
|
||||
little-endian (non-standard)
|
||||
"""
|
||||
super(SegYReader2D, self).__init__(fh, textual_reel_header, binary_reel_header, extended_textual_headers,
|
||||
trace_offset_catalog, trace_length_catalog, trace_header_format,
|
||||
encoding, endian)
|
||||
self._cdp_catalog = cdp_catalog
|
||||
self._cdp_numbers = None
|
||||
|
||||
def __getstate__(self):
|
||||
# As we're pickling, force evaluation of these so they'll be cached
|
||||
_ = self.cdp_numbers()
|
||||
state = super().__getstate__()
|
||||
return state
|
||||
|
||||
def _dimensionality(self):
|
||||
return 2
|
||||
|
||||
def cdp_numbers(self):
|
||||
"""A sorted immutable collection of CDP numbers.
|
||||
|
||||
Test for membership in this collection to determine if a particular CDP
|
||||
exists or iterate over this collection to generate all CDP numbers in
|
||||
order.
|
||||
|
||||
Returns:
|
||||
A sorted immutable collection of CDP numbers which supports the
|
||||
Sized, Iterable, Container and Sequence protocols.
|
||||
"""
|
||||
if self._cdp_numbers is None:
|
||||
self._cdp_numbers = make_sorted_distinct_sequence(self._cdp_catalog.keys())
|
||||
return self._cdp_numbers
|
||||
|
||||
def num_cdps(self):
|
||||
"""The number of distinct CDPs.
|
||||
|
||||
This number is not necessarily the same as the value returned by
|
||||
len(reader.cdp_range()) as there may be missing CDPs.
|
||||
"""
|
||||
return len(self._cdp_catalog)
|
||||
|
||||
def has_trace_index(self, cdp_number):
|
||||
"""Determine whether a specified trace_samples exists.
|
||||
|
||||
Args:
|
||||
cdp_number: A CDP number.
|
||||
|
||||
Returns:
|
||||
True if the trace_samples exists, otherwise False.
|
||||
"""
|
||||
return self._cdp_catalog[cdp_number]
|
||||
|
||||
def trace_index(self, cdp_number):
|
||||
"""Obtain the trace_samples index given an xline and a inline.
|
||||
|
||||
Args:
|
||||
cdp_number: A CDP number.
|
||||
|
||||
Returns:
|
||||
A trace_samples index which can be used with trace_samples().
|
||||
"""
|
||||
return self._cdp_catalog[cdp_number]
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
import sys
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
class ProgressBar(object):
|
||||
|
||||
def __init__(self, num_chars, character='.'):
|
||||
self._num_chars = num_chars
|
||||
self._character = character
|
||||
self._ratchet = 0
|
||||
|
||||
def __call__(self, proportion):
|
||||
existing = self._num_marks(self._ratchet)
|
||||
required = self._num_marks(proportion)
|
||||
print(self._character * (required - existing), end='')
|
||||
self._ratchet = proportion
|
||||
|
||||
def _num_marks(self, p):
|
||||
return int(round(p * self._num_chars))
|
||||
|
||||
filename = argv[0]
|
||||
|
||||
with open(filename, 'rb') as segy_file:
|
||||
segy_reader = create_reader(segy_file, progress=ProgressBar(30))
|
||||
print()
|
||||
print("Filename: ", segy_reader.filename)
|
||||
print("SEG Y revision: ", segy_reader.revision)
|
||||
print("Number of traces: ", segy_reader.num_traces())
|
||||
print("Data format: ",
|
||||
segy_reader.data_sample_format_description)
|
||||
print("Dimensionality: ", segy_reader.dimensionality)
|
||||
|
||||
try:
|
||||
print("Number of CDPs: ", segy_reader.num_cdps())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
print("Number of inlines: ", segy_reader.num_inlines())
|
||||
print("Number of crosslines: ", segy_reader.num_xlines())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
print("=== BEGIN TEXTUAL REEL HEADER ===")
|
||||
for line in segy_reader.textual_reel_header:
|
||||
print(line[3:])
|
||||
print("=== END TEXTUAL REEL HEADER ===")
|
||||
print()
|
||||
print("=== BEGIN EXTENDED TEXTUAL HEADER ===")
|
||||
print(segy_reader.extended_textual_header)
|
||||
print("=== END EXTENDED TEXTUAL_HEADER ===")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,65 @@
|
||||
"""SEG Y Revision numbers
|
||||
|
||||
From the specification:
|
||||
|
||||
SEG Y Format Revision Number. This is a 16-bit unsigned value with a Q- point between the first and second bytes.
|
||||
Thus for SEG Y Revision 1.0, as defined in this document, this will be recorded as 0100 in base 16.
|
||||
"""
|
||||
|
||||
# It's not at all obvious to me whether this means 0xHH.LL where the value is in binary-coded decimal, (in which case
|
||||
# version 1.1 would be represented as 0x0101, or whether it is in fixed-point binary, in which case we can't represent
|
||||
# version 1.1. Until I learn otherwise, I'm going for the BCD interpretation.
|
||||
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
SEGY_REVISION_0 = 0x0000
|
||||
SEGY_REVISION_1 = 0x0100
|
||||
|
||||
VARIANTS = {
|
||||
SEGY_REVISION_0: SEGY_REVISION_0, # Ensure that SEGY_REVISION_0 maps to itself
|
||||
SEGY_REVISION_1: SEGY_REVISION_1, # Ensure that SEGY_REVISION_1 maps to itself
|
||||
1: SEGY_REVISION_1, # Common, but erroneous, decimal one
|
||||
100: SEGY_REVISION_1} # Common, but erroneous, decimal one-hundred
|
||||
|
||||
|
||||
class SegYRevisionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def canonicalize_revision(revision):
|
||||
"""Canonicalize a SEG Y revision.
|
||||
|
||||
Various SEG Y revisions are seen in the wild; this function canonicalizes the supplies revision
|
||||
to either SEGY_REVISION_0 or SEGY_REVISION_1.
|
||||
|
||||
Args:
|
||||
revision: Any object representing a SEG Y revision.
|
||||
|
||||
Returns:
|
||||
An integer revision - either SEGY_REVISION_0 or SEGY_REVISION_1.
|
||||
|
||||
Raises:
|
||||
SegYRevisionError: If the revision is not known.
|
||||
"""
|
||||
try:
|
||||
return VARIANTS[revision]
|
||||
except KeyError:
|
||||
raise SegYRevisionError("Unknown SEG Y Revision raw={!r} hex={} decimal={}".format(
|
||||
revision, hex(revision), integer_to_decimal_revision(revision)))
|
||||
|
||||
|
||||
def integer_to_decimal_revision(revision):
|
||||
"""Convert a SEG Y revision integer into decimal form.
|
||||
|
||||
Args:
|
||||
revision: An canonical revision integer (e.g. as produced by
|
||||
a call to canonicalize_revision().
|
||||
|
||||
Returns:
|
||||
A decimal real number.
|
||||
"""
|
||||
lo = revision & 0xFF
|
||||
hi = (revision >> 8) & 0xFF
|
||||
return Decimal(hi) + Decimal(lo)/Decimal(10)
|
||||
@@ -0,0 +1,75 @@
|
||||
|
||||
from bisect import bisect_left
|
||||
from collections.abc import Sequence, Set
|
||||
from itertools import chain
|
||||
|
||||
|
||||
class SortedFrozenSet(Sequence, Set):
|
||||
|
||||
def __new__(cls, items=None):
|
||||
if type(items) == cls:
|
||||
return items
|
||||
obj = object.__new__(cls)
|
||||
obj._items = sorted(set(items)) if items is not None else []
|
||||
return obj
|
||||
|
||||
def __contains__(self, item):
|
||||
try:
|
||||
self.index(item)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def __len__(self):
|
||||
return len(self._items)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._items)
|
||||
|
||||
def __getitem__(self, index):
|
||||
result = self._items[index]
|
||||
return SortedFrozenSet(result) if isinstance(index, slice) else result
|
||||
|
||||
def __repr__(self):
|
||||
return "SortedFrozenSet({})".format(repr(self._items) if self._items else '')
|
||||
|
||||
def __eq__(self, rhs):
|
||||
if not isinstance(rhs, SortedFrozenSet):
|
||||
return False
|
||||
return self._items == rhs._items
|
||||
|
||||
def index(self, item):
|
||||
index = bisect_left(self._items, item)
|
||||
if (index != len(self._items)) and self._items[index] == item:
|
||||
return index
|
||||
raise ValueError("{} not found".format(repr(item)))
|
||||
|
||||
def count(self, item):
|
||||
return int(item in self._items)
|
||||
|
||||
def __add__(self, rhs):
|
||||
return SortedFrozenSet(chain(self._items, rhs._items))
|
||||
|
||||
def __mul__(self, rhs):
|
||||
return SortedFrozenSet(self) if rhs > 0 else SortedFrozenSet()
|
||||
|
||||
def __rmul__(self, lhs):
|
||||
return self * lhs
|
||||
|
||||
def issubset(self, iterable):
|
||||
return self <= SortedFrozenSet(iterable)
|
||||
|
||||
def issuperset(self, iterable):
|
||||
return self >= SortedFrozenSet(iterable)
|
||||
|
||||
def intersection(self, iterable):
|
||||
return self & SortedFrozenSet(iterable)
|
||||
|
||||
def union(self, iterable):
|
||||
return self | SortedFrozenSet(iterable)
|
||||
|
||||
def symmetric_difference(self, iterable):
|
||||
return self ^ SortedFrozenSet(iterable)
|
||||
|
||||
def difference(self, iterable):
|
||||
return self - SortedFrozenSet(iterable)
|
||||
@@ -0,0 +1,147 @@
|
||||
from segpy.revisions import SEGY_REVISION_0, SEGY_REVISION_1
|
||||
|
||||
TEMPLATE = """
|
||||
C 1 CLIENT { client } COMPANY { company } CREW NO {crew }
|
||||
C 2 LINE { line } AREA { area } MAP ID { map_id }
|
||||
C 3 REEL NO {reelnum} DAY-START OF REEL {d} YEAR {yr} OBSERVER {observer }
|
||||
C 4 INSTRUMENT: MFG { mfg } MODEL { model } SERIAL NO { serial }
|
||||
C 5 DATA TRACES/RECORD {dtpr} AUXILIARY TRACES/RECORD {atpr } CDP FOLD {cdpfold}
|
||||
C 6 SAMPLE INTERVAL {intvl} SAMPLES/TRACE {spt} BITS/IN {bi} BYTES/SAMPLE {bps }
|
||||
C 7 RECORDING FORMAT {rfmt} FORMAT THIS REEL {ftr } MEASUREMENT SYSTEM {measmnt}
|
||||
C 8 SAMPLE CODE: FLOATING PT {f} FIXED PT {x} FIXED PT-GAIN {g} CORRELATED {cor}
|
||||
C 9 GAIN TYPE: FIXED {i} BINARY {b} FLOATING POINT {c} OTHER {other }
|
||||
C10 FILTERS: ALIAS {a }HZ NOTCH {n }HZ BAND {b1}-{b2 }HZ SLOPE {s}-{s2}DB/OCT
|
||||
C11 SOURCE: TYPE {type } NUMBER/POINT {npt } POINT INTERVAL {point_interval }
|
||||
C12 PATTERN: {source_pattern } LENGTH {lent} WIDTH {width }
|
||||
C13 SWEEP: START {ss}HZ END {se}HZ LENGTH {lms}MS CHANNEL NO {q} TYPE {cd }
|
||||
C14 TAPER: START LENGTH {tsl }MS END LENGTH {tel }MS TYPE {taper_type }
|
||||
C15 SPREAD: OFFSET {off } MAX DISTANCE {md } GROUP INTERVAL {group_interval }
|
||||
C16 GEOPHONES: PER GROUP {p} SPACING {y} FREQUENCY {r} MFG {gmfg } MODEL {gmod}
|
||||
C17 PATTERN: {geophone_pattern } LENGTH {glen} WIDTH {geophone_width }
|
||||
C18 TRACES SORTED BY: RECORD {u} CDP {v} OTHER { sort_other }
|
||||
C19 AMPLITUDE RECOVERY: NONE {ar} SPHERICAL DIV {sd } AGC {} OTHER {ar_other }
|
||||
C20 MAP PROJECTION {map_projection } ZONE ID {zid} COORDINATE UNITS {co_units }
|
||||
C21 PROCESSING: { processing1 }
|
||||
C22 PROCESSING: { processing2 }
|
||||
C23 { unassigned1 }
|
||||
C24 { unassigned2 }
|
||||
C25 { unassigned3 }
|
||||
C26 { unassigned4 }
|
||||
C27 { unassigned5 }
|
||||
C28 { unassigned6 }
|
||||
C29 { unassigned7 }
|
||||
C30 { unassigned8 }
|
||||
C31 { unassigned9 }
|
||||
C32 { unassigned10 }
|
||||
C33 { unassigned11 }
|
||||
C34 { unassigned12 }
|
||||
C35 { unassigned13 }
|
||||
C36 { unassigned14 }
|
||||
C37 { unassigned15 }
|
||||
C38 { unassigned16 }
|
||||
C39 { unassigned17 }
|
||||
C40 { end_marker }
|
||||
"""
|
||||
|
||||
|
||||
END_TEXTUAL_HEADER = 'END TEXTUAL HEADER'
|
||||
END_EBCDIC = 'END EBCDIC'
|
||||
|
||||
END_MARKERS = {SEGY_REVISION_0: END_EBCDIC,
|
||||
SEGY_REVISION_1: END_TEXTUAL_HEADER}
|
||||
|
||||
TEMPLATE_FIELD_NAMES = {'client': 'client',
|
||||
'company': 'company',
|
||||
'crew': 'crew_number',
|
||||
'line': 'line',
|
||||
'area': 'area',
|
||||
'map_id': 'map_id',
|
||||
'reelnum': 'reel_number',
|
||||
'd': 'day_start_of_reel',
|
||||
'yr': 'year',
|
||||
'observer': 'observer',
|
||||
'mfg': 'instrument_manufacturer',
|
||||
'model': 'instrument_model',
|
||||
'serial': 'instrument_serial',
|
||||
'dtpr': 'data_traces_per_record',
|
||||
'atpr': 'auxiliary_traces_per_record',
|
||||
'cdpfold': 'cdp_fold',
|
||||
'intvl': 'sample_interval',
|
||||
'spt': 'samples_per_trace',
|
||||
'bi': 'bits_per_inch',
|
||||
'bps': 'bytes_per_sample',
|
||||
'rfmt': 'recording_format',
|
||||
'ftr': 'format_this_reel',
|
||||
'measmnt': 'measurement_system',
|
||||
'f': 'sample_code_floating_point',
|
||||
'x': 'sample_code_fixed_point',
|
||||
'g': 'sample_code_fixed_point_gain',
|
||||
'cor': 'sample_code_correlated',
|
||||
'i': 'gain_type_fixed',
|
||||
'b': 'gain_type_binary',
|
||||
'c': 'gain_type_fixed_point_gain',
|
||||
'other': 'gain_type_other',
|
||||
'a': 'filters_alias_hz',
|
||||
'n': 'filters_notch_hz',
|
||||
'b1': 'filters_band_lower_hz',
|
||||
'b2': 'filters_band_upper_hz',
|
||||
's': 'filters_slope_lower_db_per_oct',
|
||||
's2': 'filters_slope_upper_db_per_oct',
|
||||
'type': 'source_type',
|
||||
'npt': 'source_number_per_point',
|
||||
'point_interval': 'source_point_interval',
|
||||
'source_pattern': 'source_pattern',
|
||||
'lent': 'source_length',
|
||||
'width': 'source_width',
|
||||
'ss': 'sweep_start_hz',
|
||||
'se': 'sweep_end_hz',
|
||||
'lms': 'sweep_length_ms',
|
||||
'q': 'sweep_channel_number',
|
||||
'cd': 'sweep_type',
|
||||
'tsl': 'taper_start_length_ms',
|
||||
'tel': 'taper_end_length_ms',
|
||||
'taper_type': 'taper_type',
|
||||
'off': 'spread_offset',
|
||||
'md': 'spread_max_distance',
|
||||
'group_interval': 'spread_group_interval',
|
||||
'p': 'geophones_per_group',
|
||||
'y': 'geophone_spacing',
|
||||
'r': 'geophone_frequency',
|
||||
'gmfg': 'geophone_manufacturer',
|
||||
'gmod': 'geophone_model',
|
||||
'geophone_pattern': 'geophone_pattern',
|
||||
'glen': 'geophone_length',
|
||||
'geophone_width': 'geophone_width',
|
||||
'u': 'traces_sorted_by_record',
|
||||
'v': 'traces_sorted_by_cdp',
|
||||
'sort_other': 'traces_sorted_by_other',
|
||||
'ar': 'amplitude_recovery_none',
|
||||
'sd': 'amplitude_recovery_spherical_div',
|
||||
'': 'amplitude_recovery_agc',
|
||||
'ar_other': 'amplitude_recovery_other',
|
||||
'map_projection': 'map_projection',
|
||||
'zid': 'zone_id',
|
||||
'co_units': 'coordinate_units',
|
||||
'processing1': 'processing1',
|
||||
'processing2': 'processing2',
|
||||
'unassigned1': 'unassigned1',
|
||||
'unassigned2': 'unassigned2',
|
||||
'unassigned3': 'unassigned3',
|
||||
'unassigned4': 'unassigned4',
|
||||
'unassigned5': 'unassigned5',
|
||||
'unassigned6': 'unassigned6',
|
||||
'unassigned7': 'unassigned7',
|
||||
'unassigned8': 'unassigned8',
|
||||
'unassigned9': 'unassigned9',
|
||||
'unassigned10': 'unassigned10',
|
||||
'unassigned11': 'unassigned11',
|
||||
'unassigned12': 'unassigned12',
|
||||
'unassigned13': 'unassigned13',
|
||||
'unassigned14': 'unassigned14',
|
||||
'unassigned15': 'unassigned15',
|
||||
'unassigned16': 'unassigned16',
|
||||
'unassigned17': 'unassigned17',
|
||||
'end_marker': 'end_marker'
|
||||
}
|
||||
|
||||
INV_TEMPLATE_FIELD_NAMES = dict((v, k) for k, v in TEMPLATE_FIELD_NAMES.items())
|
||||
@@ -0,0 +1,927 @@
|
||||
from __future__ import print_function
|
||||
|
||||
from array import array
|
||||
from collections import OrderedDict
|
||||
from itertools import zip_longest, count
|
||||
|
||||
import os
|
||||
import struct
|
||||
import re
|
||||
import logging
|
||||
|
||||
from segpy import textual_reel_header
|
||||
from segpy.binary_reel_header import BinaryReelHeader
|
||||
from segpy.catalog import CatalogBuilder
|
||||
from segpy.datatypes import SEG_Y_TYPE_TO_CTYPE, size_in_bytes, DATA_SAMPLE_FORMAT_TO_SEG_Y_TYPE, CTYPE_TO_SIZE
|
||||
from segpy.encoding import guess_encoding, is_supported_encoding, UnsupportedEncodingError
|
||||
from segpy.header import SubFormatMeta
|
||||
from segpy.ibm_float import IBMFloat
|
||||
from segpy.packer import make_header_packer
|
||||
from segpy.revisions import canonicalize_revision
|
||||
from segpy.trace_header import TraceHeaderRev1
|
||||
from segpy.util import file_length, batched, pad, complementary_intervals, NATIVE_ENDIANNESS, EMPTY_BYTE_STRING, filename_from_handle
|
||||
|
||||
|
||||
HEADER_NEWLINE = '\r\n'
|
||||
|
||||
CARD_LENGTH = 80
|
||||
CARDS_PER_HEADER = 40
|
||||
|
||||
TEXTUAL_HEADER_NUM_BYTES = CARD_LENGTH * CARDS_PER_HEADER
|
||||
BINARY_HEADER_NUM_BYTES = 400
|
||||
REEL_HEADER_NUM_BYTES = TEXTUAL_HEADER_NUM_BYTES + BINARY_HEADER_NUM_BYTES
|
||||
TRACE_HEADER_NUM_BYTES = 240
|
||||
|
||||
END_TEXT_STANZA = "((SEG: EndText))"
|
||||
|
||||
|
||||
def logger():
|
||||
# Defer logger creation until the module is *used* rather than imported.
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
|
||||
def extract_revision(binary_reel_header):
|
||||
"""Obtain the SEG Y revision from the reel header.
|
||||
|
||||
Args:
|
||||
binary_reel_header: A dictionary containing a reel header, such as obtained
|
||||
from read_binary_reel_header()
|
||||
|
||||
Returns:
|
||||
One of the constants revisions.SEGY_REVISION_0 or
|
||||
revisions.SEGY_REVISION_1
|
||||
"""
|
||||
raw_revision = binary_reel_header.format_revision_num
|
||||
return canonicalize_revision(raw_revision)
|
||||
|
||||
|
||||
def num_extended_textual_headers(binary_reel_header):
|
||||
"""Obtain the number of 3200 byte extended textual file headers.
|
||||
|
||||
A value of zero indicates there are no Extended Textual File Header records
|
||||
(i.e. this file has no Extended Textual File Header(s)). A value of -1 indicates
|
||||
that there are a variable number of Extended Textual File Header records and the
|
||||
end of the Extended Textual File Header is denoted by an ((SEG: EndText)) stanza
|
||||
in the final record. A positive value indicates that there are exactly that many
|
||||
Extended Textual File Header records.
|
||||
"""
|
||||
# TODO: Is this method needed any more?
|
||||
num_ext_headers = binary_reel_header.num_extended_textual_headers
|
||||
return num_ext_headers
|
||||
|
||||
|
||||
def bytes_per_sample(binary_reel_header, revision):
|
||||
"""Determine the number of bytes per sample from the reel header.
|
||||
|
||||
Args:
|
||||
binary_reel_header: A header object.
|
||||
|
||||
revision: One of the constants revisions.SEGY_REVISION_0 or
|
||||
revisions.SEGY_REVISION_1
|
||||
|
||||
Returns:
|
||||
An integer number of bytes per sample.
|
||||
"""
|
||||
dsf = binary_reel_header.data_sample_format
|
||||
seg_y_type = DATA_SAMPLE_FORMAT_TO_SEG_Y_TYPE[dsf]
|
||||
ctype = SEG_Y_TYPE_TO_CTYPE[seg_y_type]
|
||||
bps = CTYPE_TO_SIZE[ctype]
|
||||
return bps
|
||||
|
||||
|
||||
def samples_per_trace(binary_reel_header):
|
||||
"""Determine the number of samples per trace_samples from the reel header.
|
||||
|
||||
Note: There is no requirement for all traces to be of the same length,
|
||||
so this value should be considered indicative only, and as such is
|
||||
mostly useful in the absence of other information. The actual number
|
||||
of samples for a specific trace_samples should be retrieved from individual
|
||||
trace_samples headers.
|
||||
|
||||
Args:
|
||||
binary_reel_header: A dictionary containing a reel header, such as obtained
|
||||
from read_binary_reel_header()
|
||||
|
||||
Returns:
|
||||
An integer number of samples per trace_samples
|
||||
"""
|
||||
return binary_reel_header.num_samples
|
||||
|
||||
|
||||
def trace_length_bytes(binary_reel_header, bps):
|
||||
"""Determine the trace_samples length in bytes from the reel header.
|
||||
|
||||
Note: There is no requirement for all traces to be of the same length,
|
||||
so this value should be considered indicative only, and as such is
|
||||
mostly useful in the absence of other information. The actual number
|
||||
of samples for a specific trace_samples should be retrieved from individual
|
||||
trace_samples headers.
|
||||
|
||||
Args:
|
||||
binary_reel_header: A dictionary containing a reel header, such as obtained
|
||||
from read_binary_reel_header()
|
||||
|
||||
bps: The number of bytes per sample, such as obtained from a call to
|
||||
bytes_per_sample()
|
||||
|
||||
"""
|
||||
return samples_per_trace(binary_reel_header) * bps + TRACE_HEADER_NUM_BYTES
|
||||
|
||||
|
||||
def guess_textual_header_encoding(fh):
|
||||
fh.seek(0)
|
||||
raw_header = fh.read(TEXTUAL_HEADER_NUM_BYTES)
|
||||
encoding = guess_encoding(raw_header)
|
||||
return encoding
|
||||
|
||||
|
||||
def read_textual_reel_header(fh, encoding):
|
||||
"""Read the SEG Y card image header, also known as the textual header
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode positioned such that the
|
||||
beginning of the textual header will be the next byte to read.
|
||||
|
||||
encoding: Either 'cp037' for EBCDIC or 'ascii' for ASCII.
|
||||
|
||||
Returns:
|
||||
A tuple of forty Unicode strings containing the transcoded header data.
|
||||
"""
|
||||
fh.seek(0)
|
||||
raw_header = fh.read(TEXTUAL_HEADER_NUM_BYTES)
|
||||
|
||||
num_bytes_read = len(raw_header)
|
||||
if num_bytes_read < TEXTUAL_HEADER_NUM_BYTES:
|
||||
raise EOFError("Only {} bytes of {} byte textual reel header could be read"
|
||||
.format(num_bytes_read, TEXTUAL_HEADER_NUM_BYTES))
|
||||
|
||||
lines = tuple(bytes(raw_line).decode(encoding) for raw_line in batched(raw_header, CARD_LENGTH))
|
||||
return lines
|
||||
|
||||
|
||||
def read_binary_reel_header(fh, endian='>'):
|
||||
"""Read the SEG Y binary reel header.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode. Binary header is assumed to
|
||||
be at an offset of 3200 bytes from the beginning of the file.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<' for
|
||||
little-endian (non-standard)
|
||||
"""
|
||||
header_packer = make_header_packer(BinaryReelHeader, endian)
|
||||
buffer = fh.read(BinaryReelHeader.LENGTH_IN_BYTES)
|
||||
reel_header = header_packer.unpack(buffer)
|
||||
return reel_header
|
||||
|
||||
|
||||
def has_end_text_stanza(ext_header):
|
||||
"""Determine whether the header is the end text stanza.
|
||||
|
||||
Args:
|
||||
ext_header: A sequence of forty CARD_LENGTH character Unicode strings.
|
||||
|
||||
Returns:
|
||||
True if the header is the SEG Y Revision 1 end text header,
|
||||
otherwise False.
|
||||
"""
|
||||
return END_TEXT_STANZA in ext_header[0]
|
||||
|
||||
|
||||
def read_extended_headers_until_end(fh, encoding):
|
||||
"""Read an unspecified number of extended textual headers, until the end-text header is found.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode. The first of any extended textual headers
|
||||
is assumed to be at an offset of 3600 bytes from the beginning of the file
|
||||
(immediately following the binary reel header).
|
||||
|
||||
encoding: Optional encoding of the header in the file. If None (the
|
||||
default) a reliable heuristic will be used to guess the encoding.
|
||||
Typically 'cp037' for EBCDIC or 'ascii' for ASCII.
|
||||
|
||||
Returns:
|
||||
A list of tuples each containing forty CARD_LENGTH character Unicode strings. If present, the end_text
|
||||
stanza is excluded.
|
||||
"""
|
||||
extended_headers = []
|
||||
while True:
|
||||
ext_header = read_textual_reel_header(fh, encoding)
|
||||
extended_headers.append(ext_header)
|
||||
if has_end_text_stanza(ext_header):
|
||||
break
|
||||
return extended_headers
|
||||
|
||||
|
||||
def read_extended_headers_counted(fh, num_expected, encoding):
|
||||
"""Read a specified number of extended textual headers.
|
||||
|
||||
If an end-text stanza is located prematurely (in anything other than the last expected header)
|
||||
reading will be terminated and a warning logged.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode. The first of any extended textual headers
|
||||
is assumed to be at an offset of 3600 bytes from the beginning of the file
|
||||
(immediately following the binary reel header).
|
||||
|
||||
num_expected: A non-negative integer of headers.
|
||||
|
||||
encoding: Optional encoding of the header in the file. If None (the
|
||||
default) a reliable heuristic will be used to guess the encoding.
|
||||
Typically 'cp037' for EBCDIC or 'ascii' for ASCII.
|
||||
|
||||
Returns:
|
||||
A list of tuples each containing forty CARD_LENGTH -character Unicode strings.
|
||||
"""
|
||||
assert num_expected >= 0
|
||||
extended_headers = []
|
||||
for i in range(num_expected):
|
||||
ext_header = read_textual_reel_header(fh, encoding)
|
||||
if has_end_text_stanza(ext_header):
|
||||
if i != num_expected - 1:
|
||||
logger().warning("Unexpected end-text extended header.")
|
||||
break
|
||||
extended_headers.append(ext_header)
|
||||
|
||||
return extended_headers
|
||||
|
||||
|
||||
def read_extended_textual_headers(fh, binary_reel_header, encoding):
|
||||
"""Read any extended textual reel headers.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode. The first of any extended textual headers
|
||||
is assumed to be at an offset of 3600 bytes from the beginning of the file
|
||||
(immediately following the binary reel header).
|
||||
|
||||
binary_reel_header: A dictionary containing data read from the binary
|
||||
reel header by the read_binary_reel_header() function.
|
||||
|
||||
encoding: Optional encoding of the header in the file. If None (the
|
||||
default) a reliable heuristic will be used to guess the encoding.
|
||||
Typically 'cp037' for EBCDIC or 'ascii' for ASCII.
|
||||
|
||||
Returns:
|
||||
A sequence of sequences of Unicode strings representing headers of lines of characters. The length of the
|
||||
outer sequence will be equal to the number of extended headers read. Each item in the outer sequence will be
|
||||
a sequence of exactly forty Unicode strings. To combine the headers into a single string, consider using
|
||||
concatenate_extended_textual_headers().
|
||||
|
||||
Post-condition:
|
||||
As a post-condition to this function, the file-pointer of fh will be
|
||||
positioned immediately after the last extended textual header, which
|
||||
should be the start of the first trace_samples header.
|
||||
"""
|
||||
fh.seek(REEL_HEADER_NUM_BYTES)
|
||||
declared_num_ext_headers = num_extended_textual_headers(binary_reel_header)
|
||||
|
||||
if declared_num_ext_headers < 0:
|
||||
return read_extended_headers_until_end(fh, encoding)
|
||||
|
||||
return read_extended_headers_counted(fh, declared_num_ext_headers, encoding)
|
||||
|
||||
|
||||
_READ_PROPORTION = 0.75 # The proportion of time spent in catalog_traces
|
||||
# reading the file. Determined empirically.
|
||||
|
||||
|
||||
def catalog_traces(fh, bps, trace_header_format=TraceHeaderRev1, endian='>', progress=None):
|
||||
"""Build catalogs to facilitate random access to trace_samples data.
|
||||
|
||||
Note:
|
||||
This function can take significant time to run, proportional
|
||||
to the number of traces in the SEG Y file.
|
||||
|
||||
Four catalogs will be build:
|
||||
|
||||
1. A catalog mapping trace_samples index (0-based) to the position of that
|
||||
trace_samples header in the file.
|
||||
|
||||
2. A catalog mapping trace_samples index (0-based) to the number of
|
||||
samples in that trace_samples.
|
||||
|
||||
3. A catalog mapping CDP number to the trace_samples index.
|
||||
|
||||
4. A catalog mapping an (inline, crossline) number 2-tuple to
|
||||
trace_samples index.
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open in binary mode, positioned at the
|
||||
start of the first trace_samples header.
|
||||
|
||||
bps: The number of bytes per sample, such as obtained by a call
|
||||
to bytes_per_sample()
|
||||
|
||||
trace_header_format: The class defining the trace header format.
|
||||
Defaults to TraceHeaderRev1.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
|
||||
progress: A unary callable which will be passed a number
|
||||
between zero and one indicating the progress made. If
|
||||
provided, this callback will be invoked at least once with
|
||||
an argument equal to 1
|
||||
|
||||
Returns:
|
||||
A 4-tuple of the form (trace_samples-offset-catalog,
|
||||
trace_samples-length-catalog,
|
||||
cdp-catalog,
|
||||
line-catalog)` where
|
||||
each catalog is an instance of ``collections.Mapping`` or None
|
||||
if no catalog could be built.
|
||||
"""
|
||||
progress_callback = progress if progress is not None else lambda p: None
|
||||
|
||||
if not callable(progress_callback):
|
||||
raise TypeError("catalog_traces(): progress callback must be callable")
|
||||
|
||||
class CatalogSubFormat(metaclass=SubFormatMeta,
|
||||
parent_format=trace_header_format,
|
||||
parent_field_names=(
|
||||
'file_sequence_num',
|
||||
'ensemble_num',
|
||||
'num_samples',
|
||||
'inline_number',
|
||||
'crossline_number',
|
||||
)):
|
||||
pass
|
||||
|
||||
trace_header_packer = make_header_packer(CatalogSubFormat, endian)
|
||||
|
||||
length = file_length(fh)
|
||||
|
||||
pos_begin = fh.tell()
|
||||
|
||||
trace_offset_catalog_builder = CatalogBuilder()
|
||||
trace_length_catalog_builder = CatalogBuilder()
|
||||
line_catalog_builder = CatalogBuilder()
|
||||
alt_line_catalog_builder = CatalogBuilder()
|
||||
cdp_catalog_builder = CatalogBuilder()
|
||||
|
||||
for trace_number in count():
|
||||
progress_callback(_READ_PROPORTION * pos_begin / length)
|
||||
fh.seek(pos_begin)
|
||||
data = fh.read(TRACE_HEADER_NUM_BYTES)
|
||||
if len(data) < TRACE_HEADER_NUM_BYTES:
|
||||
break
|
||||
trace_header = trace_header_packer.unpack(data)
|
||||
|
||||
num_samples = trace_header.num_samples
|
||||
trace_length_catalog_builder.add(trace_number, num_samples)
|
||||
samples_bytes = num_samples * bps
|
||||
trace_offset_catalog_builder.add(trace_number, pos_begin)
|
||||
# Should we check the data actually exists?
|
||||
line_catalog_builder.add((trace_header.inline_number,
|
||||
trace_header.crossline_number),
|
||||
trace_number)
|
||||
alt_line_catalog_builder.add((trace_header.file_sequence_num,
|
||||
trace_header.ensemble_num),
|
||||
trace_number)
|
||||
cdp_catalog_builder.add(trace_header.ensemble_num, trace_number)
|
||||
pos_end = pos_begin + TRACE_HEADER_NUM_BYTES + samples_bytes
|
||||
pos_begin = pos_end
|
||||
|
||||
progress_callback(_READ_PROPORTION)
|
||||
|
||||
trace_offset_catalog = trace_offset_catalog_builder.create()
|
||||
progress_callback(_READ_PROPORTION + (_READ_PROPORTION / 4))
|
||||
|
||||
trace_length_catalog = trace_length_catalog_builder.create()
|
||||
progress_callback(_READ_PROPORTION + (_READ_PROPORTION / 2))
|
||||
|
||||
cdp_catalog = cdp_catalog_builder.create()
|
||||
progress_callback(_READ_PROPORTION + (_READ_PROPORTION * 3 / 4))
|
||||
|
||||
line_catalog = line_catalog_builder.create()
|
||||
|
||||
if line_catalog is None:
|
||||
# Some 3D files put Inline and Crossline numbers in (TraceSequenceFile, cdp) pair
|
||||
line_catalog = alt_line_catalog_builder.create()
|
||||
|
||||
progress_callback(1)
|
||||
|
||||
return (trace_offset_catalog,
|
||||
trace_length_catalog,
|
||||
cdp_catalog,
|
||||
line_catalog)
|
||||
|
||||
|
||||
def catalog_fixed_length_traces(fh, binary_reel_header, trace_header_format=TraceHeaderRev1, endian='>', progress=None):
|
||||
"""Build catalogs to for a fixed length SEG Y file. This is much faster
|
||||
than the full catalog, but has limitations. No CDP, or inline, xline
|
||||
catalogs, and it only works for segy files with fixed legth SEG Y files.
|
||||
|
||||
Note:
|
||||
This function is faster than the full catalog, but has limitations.
|
||||
No CDP, or inline, xline catalogs, and it only works for SEG Y files
|
||||
with a fixed number of samples in each trace.
|
||||
|
||||
Two catalogs will be built:
|
||||
|
||||
1. A catalog mapping trace_samples index (0-based) to the position of that
|
||||
trace_samples header in the file.
|
||||
|
||||
2. A catalog mapping trace_samples index (0-based) to the number of
|
||||
samples in that trace_samples.
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open in binary mode, positioned at the
|
||||
start of the first trace_samples header.
|
||||
|
||||
bps: The number of bytes per sample, such as obtained by a call
|
||||
to bytes_per_sample()
|
||||
|
||||
trace_header_format: The class defining the trace header format.
|
||||
Defaults to TraceHeaderRev1.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
|
||||
progress: A unary callable which will be passed a number
|
||||
between zero and one indicating the progress made. If
|
||||
provided, this callback will be invoked at least once with
|
||||
an argument equal to 1
|
||||
|
||||
Returns:
|
||||
A 4-tuple of the form (trace_samples-offset-catalog,
|
||||
trace_samples-length-catalog,
|
||||
None,
|
||||
None)` where
|
||||
each catalog is an instance of ``collections.Mapping`` or None
|
||||
if no catalog could be built.
|
||||
"""
|
||||
|
||||
revision = extract_revision(binary_reel_header)
|
||||
bps = bytes_per_sample(binary_reel_header, revision)
|
||||
|
||||
progress_callback = progress if progress is not None else lambda p: None
|
||||
|
||||
if not callable(progress_callback):
|
||||
raise TypeError("catalog_traces(): progress callback must be callable")
|
||||
|
||||
class CatalogSubFormat(metaclass=SubFormatMeta,
|
||||
parent_format=trace_header_format,
|
||||
parent_field_names=(
|
||||
'file_sequence_num',
|
||||
'ensemble_num',
|
||||
'num_samples',
|
||||
'inline_number',
|
||||
'crossline_number',
|
||||
)):
|
||||
pass
|
||||
|
||||
num_file_bytes = file_length(fh)
|
||||
num_samples=binary_reel_header.num_samples
|
||||
num_traces_float = (num_file_bytes-REEL_HEADER_NUM_BYTES)/(TRACE_HEADER_NUM_BYTES+num_samples*bps)
|
||||
num_traces = int(num_traces_float)
|
||||
if num_traces != num_traces_float:
|
||||
raise ValueError(
|
||||
"SEG Y file {!r} of {} bytes is not consistent with a fixed trace length".format(
|
||||
filename_from_handle(fh),
|
||||
num_file_bytes))
|
||||
|
||||
trace_offset_catalog_builder = CatalogBuilder()
|
||||
trace_length_catalog_builder = CatalogBuilder()
|
||||
|
||||
for trace_index in range(num_traces):
|
||||
pos_begin=REEL_HEADER_NUM_BYTES+(num_samples * bps+TRACE_HEADER_NUM_BYTES) * trace_index
|
||||
|
||||
trace_length_catalog_builder.add(trace_index, num_samples)
|
||||
trace_offset_catalog_builder.add(trace_index, pos_begin)
|
||||
|
||||
trace_offset_catalog = trace_offset_catalog_builder.create()
|
||||
trace_length_catalog = trace_length_catalog_builder.create()
|
||||
|
||||
progress_callback(1)
|
||||
|
||||
return (trace_offset_catalog,
|
||||
trace_length_catalog,
|
||||
None,
|
||||
None)
|
||||
|
||||
|
||||
def read_trace_header(fh, trace_header_packer, pos=None):
|
||||
"""Read a trace_samples header.
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open in binary mode.
|
||||
|
||||
trace_header_format: A Struct object, such as obtained from a
|
||||
call to compile_trace_header_format()
|
||||
|
||||
pos: The file offset in bytes from the beginning from which the data
|
||||
is to be read.
|
||||
|
||||
Returns:
|
||||
A TraceHeader object.
|
||||
"""
|
||||
if pos is not None:
|
||||
fh.seek(pos)
|
||||
data = fh.read(TRACE_HEADER_NUM_BYTES)
|
||||
# trace_header = TraceHeader._make(
|
||||
# trace_header_format.unpack(data))
|
||||
trace_header = trace_header_packer.unpack(data)
|
||||
return trace_header
|
||||
|
||||
|
||||
def read_binary_values(fh, pos=None, seg_y_type='int32', num_items=1, endian='>'):
|
||||
"""Read a series of values from a binary file.
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open in binary mode.
|
||||
|
||||
seg_y_type: The SEG Y data type.
|
||||
|
||||
num_items: The number of items to be read.
|
||||
Returns:
|
||||
A sequence containing count items.
|
||||
"""
|
||||
ctype = SEG_Y_TYPE_TO_CTYPE[seg_y_type]
|
||||
item_size = size_in_bytes(ctype)
|
||||
block_size = item_size * num_items
|
||||
|
||||
fh.seek(pos, os.SEEK_SET)
|
||||
buf = fh.read(block_size)
|
||||
|
||||
if len(buf) < block_size:
|
||||
raise EOFError("{} bytes requested but only {} available".format(
|
||||
block_size, len(buf)))
|
||||
|
||||
values = (unpack_ibm_floats(buf, num_items)
|
||||
if ctype == 'ibm'
|
||||
else unpack_values(buf, ctype, endian))
|
||||
assert len(values) == num_items
|
||||
return values
|
||||
|
||||
|
||||
def unpack_ibm_floats(data, num_items):
|
||||
"""Unpack a series of binary-encoded big-endian single-precision IBM floats.
|
||||
|
||||
Args:
|
||||
data: A sequence of bytes.
|
||||
|
||||
num_items: The number of floats to be read.
|
||||
|
||||
Returns:
|
||||
A sequence of floats.
|
||||
"""
|
||||
return [IBMFloat.from_bytes(data[i: i+4]) for i in range(0, num_items * 4, 4)]
|
||||
|
||||
|
||||
def unpack_values(buf, ctype, endian='>'):
|
||||
"""Unpack a series items from a byte string.
|
||||
|
||||
Args:
|
||||
data: A sequence of bytes.
|
||||
|
||||
ctype: A format code (one of the values in the datatype.CTYPES
|
||||
dictionary)
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
|
||||
Returns:
|
||||
A sequence of objects with type corresponding to the format code.
|
||||
"""
|
||||
a = array(ctype, buf)
|
||||
if endian != NATIVE_ENDIANNESS:
|
||||
a.byteswap()
|
||||
return a
|
||||
|
||||
|
||||
def format_standard_textual_header(revision, **kwargs):
|
||||
"""Produce a standard SEG Y textual header.
|
||||
|
||||
Args:
|
||||
revision: The SEG Y revision.
|
||||
|
||||
**kwargs: Named arguments corresponding to the values in the
|
||||
textual_reel_header.TEMPLATE_FIELD_NAMES dictionary,
|
||||
which in turn correspond to the placeholders in the
|
||||
textual_reel_header.TEMPLATE string. Any omitted
|
||||
arguments will result in placeholders being replaced by spaces.
|
||||
If the end_marker argument is not supplied, an appropriate end
|
||||
marker will be selected based on the SEG Y revision. For standard
|
||||
end markers consider using textual_reel_header.END_TEXTUAL_HEADER
|
||||
or textual_reel_header.END_EBCDIC.
|
||||
|
||||
Returns:
|
||||
A list of forty Unicode strings.
|
||||
|
||||
Usage:
|
||||
header = format_standard_textual_header(1,
|
||||
client="Lundin",
|
||||
company="Western Geco",
|
||||
crew_number=123,
|
||||
processing1="Sixty North AS",
|
||||
sweep_start_hz=10,
|
||||
sweep_end_hz=1000,
|
||||
sweep_length_ms=10000,
|
||||
sweep_channel_number=3,
|
||||
sweep_type='spread')
|
||||
|
||||
"""
|
||||
|
||||
kwargs.setdefault('end_marker', textual_reel_header.END_MARKERS[revision])
|
||||
|
||||
template = textual_reel_header.TEMPLATE
|
||||
|
||||
placeholder_slices = parse_template(template)
|
||||
background_slices = complementary_intervals(placeholder_slices.values(), 0, len(template))
|
||||
|
||||
chunks = []
|
||||
for bg_slice, placeholder in zip_longest(background_slices, placeholder_slices.items()):
|
||||
|
||||
if bg_slice is not None:
|
||||
chunks.append(template[bg_slice])
|
||||
|
||||
if placeholder is not None:
|
||||
ph_name, ph_slice = placeholder
|
||||
ph_arg_name = textual_reel_header.TEMPLATE_FIELD_NAMES[ph_name]
|
||||
ph_value = kwargs.pop(ph_arg_name, '')
|
||||
ph_len = ph_slice.stop - ph_slice.start
|
||||
substitute = str(ph_value)[:ph_len].ljust(ph_len, ' ')
|
||||
chunks.append(substitute)
|
||||
|
||||
if len(kwargs) > 0:
|
||||
raise TypeError("The following keyword arguments did not correspond to template placeholders: {!r}"
|
||||
.format(list(kwargs.keys())))
|
||||
|
||||
concatenation = ''.join(chunks)
|
||||
lines = concatenation.splitlines(keepends=False)
|
||||
|
||||
return lines[1:] # Omit the first and last lines, which are artifacts of the multi-line string template
|
||||
|
||||
|
||||
_TEMPLATE_PATTERN = r'\{\s*(\w*)\s*\}'
|
||||
_TEMPLATE_REGEX = re.compile(_TEMPLATE_PATTERN)
|
||||
|
||||
|
||||
def parse_template(template):
|
||||
"""Parse a template to produce a dictionary of placeholders.
|
||||
|
||||
Args:
|
||||
template: The template string containing { field-name } style fixed-width fields.
|
||||
|
||||
Returns:
|
||||
A OrderedDict mapping field names to slices objects which can be used to index
|
||||
into the template string. The order of the entries is the same as the order within
|
||||
which they occur in the template.
|
||||
"""
|
||||
matches = _TEMPLATE_REGEX.finditer(template)
|
||||
|
||||
fields = OrderedDict()
|
||||
for match in matches:
|
||||
name = match.group(1)
|
||||
start = match.start()
|
||||
end = match.end()
|
||||
fields[name] = slice(start, end)
|
||||
|
||||
return fields
|
||||
|
||||
|
||||
def write_textual_reel_header(fh, lines, encoding):
|
||||
"""Write the SEG Y card image header, also known as the textual header.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode positioned such that the
|
||||
beginning of the textual header will be the next byte to read.
|
||||
|
||||
lines: An iterable series of forty lines, each of which must be a
|
||||
Unicode string of CARD_LENGTH characters. The first three characters
|
||||
of each line are often "C 1" to "C40" (as required by the SEG Y
|
||||
standard) although this is not enforced by this function, since
|
||||
many widespread SEG Y readers and writers do not adhere to this
|
||||
constraint. To produce a SEG Y compliant series of header lines
|
||||
consider using the format_standard_textual_header() function.
|
||||
|
||||
Any lines longer than CARD_LENGTH characters will be truncated without
|
||||
warning. Any excess lines over CARDS_PER_HEADER will be discarded. Short
|
||||
or omitted lines will be padded with spaces.
|
||||
|
||||
encoding: Either 'cp037' for EBCDIC or 'ascii' for ASCII.
|
||||
|
||||
Post-condition:
|
||||
The file pointer in fh will be positioned at the first byte following the textual
|
||||
header.
|
||||
|
||||
Raises:
|
||||
UnsupportedEncodingError: If encoding is neither EBCDIC nor ASCII.
|
||||
UnicodeError: If the data provided in lines cannot be encoded with the encoding.
|
||||
"""
|
||||
if not is_supported_encoding(encoding):
|
||||
raise UnsupportedEncodingError("Writing textual reel header", encoding)
|
||||
|
||||
padded_lines = [line.encode(encoding).ljust(CARD_LENGTH, ' '.encode(encoding))[:CARD_LENGTH]
|
||||
for line in pad(lines, padding='', size=CARDS_PER_HEADER)]
|
||||
joined_header = EMPTY_BYTE_STRING.join(padded_lines)
|
||||
assert len(joined_header) == 3200
|
||||
fh.write(joined_header)
|
||||
|
||||
|
||||
def write_binary_reel_header(fh, binary_reel_header, endian='>'):
|
||||
"""Write the binary_reel_header to the given file-like object.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode for writing.
|
||||
|
||||
binary_reel_header: A header object.
|
||||
|
||||
Post-condition:
|
||||
The file pointer for fh will be positioned at the first byte following
|
||||
the binary reel header.
|
||||
"""
|
||||
header_packer = make_header_packer(type(binary_reel_header), endian)
|
||||
buffer = header_packer.pack(binary_reel_header)
|
||||
fh.write(buffer)
|
||||
|
||||
|
||||
def format_extended_textual_header(text, encoding, include_text_stop=False):
|
||||
"""Format a string into pages and line suitable for an extended textual header.
|
||||
|
||||
Args
|
||||
text: An arbitrary text string. Any universal newlines will be preserved.
|
||||
|
||||
encoding: Either ASCII ('ascii') or EBCDIC ('cp037')
|
||||
|
||||
include_text_stop: If True, a text stop stanza header will be appended, otherwise not.
|
||||
"""
|
||||
|
||||
if not is_supported_encoding(encoding):
|
||||
raise UnsupportedEncodingError("Extended textual header", encoding)
|
||||
|
||||
# According to the standard: "The Extended Textual File Header consists of one or more 3200-byte records, each
|
||||
# record containing 40 lines of textual card-image text." It goes on "... Each line in an Extended Textual File
|
||||
# Header ends in carriage return and linefeed (EBCDIC 0D25 or ASCII 0D0A)." Given that we're dealing with fixed-
|
||||
# length (80 byte) lines, this implies that we have 78 bytes of space into which we can encode the content of each
|
||||
# line, which must be left-justified and padded with spaces.
|
||||
|
||||
width = CARD_LENGTH - len(HEADER_NEWLINE)
|
||||
original_lines = text.splitlines()
|
||||
|
||||
# Split overly long lines (i.e. > 78) and pad too-short lines with spaces
|
||||
lines = []
|
||||
for original_line in original_lines:
|
||||
padded_lines = (pad_and_terminate_header_line(original_line[i:i+width], width)
|
||||
for i in range(0, len(original_line), width))
|
||||
lines.extend(padded_lines)
|
||||
|
||||
pages = list(batched(lines, 40, pad_and_terminate_header_line('', width)))
|
||||
|
||||
if include_text_stop:
|
||||
stop_page = format_extended_textual_header(END_TEXT_STANZA, encoding)[0]
|
||||
pages.append(stop_page)
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
def pad_and_terminate_header_line(line, width):
|
||||
return line.ljust(width, ' ') + HEADER_NEWLINE
|
||||
|
||||
|
||||
def write_extended_textual_headers(fh, pages, encoding):
|
||||
"""Write extended textual headers.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode for writing.
|
||||
|
||||
pages: An iterables series of sequences of Unicode strings, where the outer iterable
|
||||
represents 3200 byte pages, each comprised of a sequence of exactly 40 strings of nominally 80 characters
|
||||
each. Although Unicode strings are accepted, and when encoded they should result in exact 80 bytes
|
||||
sequences. To produce a valid data structure for pages, consider using format_extended_textual_header()
|
||||
|
||||
encoding: Either 'cp037' for EBCDIC or 'ascii' for ASCII.
|
||||
|
||||
Post-condition:
|
||||
The file pointer in fh will be position at the first byte after the extended textual headers, which is
|
||||
also the first byte of the first trace-header.
|
||||
|
||||
Raises:
|
||||
ValueError: If the provided header data has the wrong shape.
|
||||
UnicodeError: If the textual data could not be encoded into the specified encoding.
|
||||
|
||||
"""
|
||||
|
||||
if not is_supported_encoding(encoding):
|
||||
raise UnsupportedEncodingError("Writing extended textual header", encoding)
|
||||
|
||||
fh.seek(REEL_HEADER_NUM_BYTES)
|
||||
|
||||
encoded_pages = []
|
||||
for page_index, page in enumerate(pages):
|
||||
encoded_page = []
|
||||
# TODO: Share some of this code with writing the textual reel header.
|
||||
for line_index, line in enumerate(page):
|
||||
encoded_line = line.encode(encoding)
|
||||
num_encoded_bytes = len(encoded_line)
|
||||
if num_encoded_bytes != CARD_LENGTH:
|
||||
raise ValueError("Extended textual header line {} of page {} at {} bytes is not "
|
||||
"{} bytes".format(line_index, page_index, num_encoded_bytes, CARD_LENGTH))
|
||||
encoded_page.append(encoded_line)
|
||||
num_encoded_lines = len(encoded_page)
|
||||
if num_encoded_lines != CARDS_PER_HEADER:
|
||||
raise ValueError("Extended textual header page {} number of "
|
||||
"lines {} is not {}".format(num_encoded_lines, CARDS_PER_HEADER))
|
||||
encoded_pages.append(encoded_page)
|
||||
|
||||
for encoded_page in encoded_pages:
|
||||
concatenated_page = EMPTY_BYTE_STRING.join(encoded_page)
|
||||
assert(len(concatenated_page) == TEXTUAL_HEADER_NUM_BYTES)
|
||||
fh.write(concatenated_page)
|
||||
|
||||
|
||||
def write_trace_header(fh, trace_header, trace_header_packer, pos=None):
|
||||
"""Write a TraceHeader to file.
|
||||
|
||||
Args:
|
||||
fh: A file-like object open in binary mode for writing.
|
||||
|
||||
trace_header: A TraceHeader object.
|
||||
|
||||
trace_header_packer: A Packer object configured for the trace
|
||||
header format.
|
||||
|
||||
pos: An optional file offset in bytes from the beginning of the
|
||||
file. Defaults to the current file position.
|
||||
"""
|
||||
if pos is not None:
|
||||
fh.seek(pos, os.SEEK_SET)
|
||||
|
||||
buf = trace_header_packer.pack(trace_header)
|
||||
fh.write(buf)
|
||||
|
||||
|
||||
def write_trace_samples(fh, samples, seg_y_type, pos=None, endian='>'):
|
||||
"""Write a trace samples to a file
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open for writing in binary mode.
|
||||
|
||||
values: An iterable series of values.
|
||||
|
||||
seg_y_type: The SEG Y data type.
|
||||
|
||||
pos: An optional offset from the beginning of the file. If omitted,
|
||||
any writing is done at the current file position.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
"""
|
||||
write_binary_values(fh, samples, seg_y_type, pos, endian)
|
||||
|
||||
|
||||
def write_binary_values(fh, values, seg_y_type, pos=None, endian='>'):
|
||||
"""Write a series of values to a file.
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open for writing in binary mode.
|
||||
|
||||
values: An iterable series of values.
|
||||
|
||||
seg_y_type: The SEG Y data type.
|
||||
|
||||
pos: An optional offset from the beginning of the file. If omitted,
|
||||
any writing is done at the current file position.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
"""
|
||||
ctype = SEG_Y_TYPE_TO_CTYPE[seg_y_type]
|
||||
|
||||
if pos is not None:
|
||||
fh.seek(pos, os.SEEK_SET)
|
||||
|
||||
buf = (pack_ibm_floats(values)
|
||||
if ctype == 'ibm'
|
||||
else pack_values(values, ctype, endian))
|
||||
|
||||
fh.write(buf)
|
||||
|
||||
|
||||
def pack_ibm_floats(values):
|
||||
"""Pack floats into binary-encoded big-endian single-precision IBM floats.
|
||||
|
||||
Args:
|
||||
values: An iterable series of numeric values.
|
||||
|
||||
Returns:
|
||||
A sequence of bytes.
|
||||
"""
|
||||
return EMPTY_BYTE_STRING.join(bytes(IBMFloat.from_real(value)) for value in values)
|
||||
|
||||
|
||||
def pack_values(values, ctype, endian='>'):
|
||||
"""Pack values into binary encoded big-endian byte strings.
|
||||
|
||||
Args:
|
||||
values: An iterable series of values.
|
||||
|
||||
fmt: A format code (one of the values in the datatype.CTYPES
|
||||
dictionary)
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
"""
|
||||
c_format = '{}{}{}'.format(endian, len(values), ctype)
|
||||
return struct.pack(c_format, *values)
|
||||
@@ -0,0 +1,561 @@
|
||||
from segpy.header import FormatMeta, field
|
||||
from segpy.types import Int32, Int16
|
||||
|
||||
|
||||
class TraceHeaderRev0(metaclass=FormatMeta):
|
||||
|
||||
START_OFFSET_IN_BYTES = 1
|
||||
LENGTH_IN_BYTES = 240
|
||||
|
||||
line_sequence_num = field(
|
||||
Int32, offset=1, default=0, documentation=
|
||||
"Trace sequence number within line — Numbers continue to increase if the same line "
|
||||
"continues across multiple SEG Y files. Highly recommended for all types of data.")
|
||||
|
||||
file_sequence_num = field(
|
||||
Int32, offset=5, default=0, documentation=
|
||||
"Trace sequence number within SEG Y file — Each file starts with trace sequence one.")
|
||||
|
||||
field_record_num = field(
|
||||
Int32, offset=9, default=0, documentation=
|
||||
"Original field record number. Highly recommended for all types of data.")
|
||||
|
||||
trace_num = field(
|
||||
Int32, offset=13, default=0, documentation=
|
||||
"Trace number within the original field record. Highly recommended for all types of data.")
|
||||
|
||||
energy_source_point_num = field(
|
||||
Int32, offset=17, default=0, documentation=
|
||||
"Energy source point number — Used when more than one record occurs at the same "
|
||||
"effective surface location. It is recommended that the new entry defined in Trace "
|
||||
"Header bytes 197-202 be used for shotpoint number.")
|
||||
|
||||
ensemble_num = field(
|
||||
Int32, offset=21, default=0, documentation=
|
||||
"Ensemble number (i.e. CDP , CMP , CRP , etc)")
|
||||
|
||||
ensemble_trace_num = field(
|
||||
Int32, offset=25, default=0, documentation=
|
||||
"Trace number within the ensemble — Each ensemble starts with trace number one.")
|
||||
|
||||
trace_identification_code = field(
|
||||
Int16, offset=29, default=0, documentation=
|
||||
"Trace identification code")
|
||||
|
||||
num_vertically_summed_traces = field(
|
||||
Int16, offset=31, default=1, documentation=
|
||||
"Number of vertically summed traces yielding this trace. (1 is one trace, 2 is two summed traces, etc.)")
|
||||
|
||||
num_horizontally_stacked_traces = field(
|
||||
Int16, offset=33, default=1, documentation=
|
||||
"Number of horizontally stacked traces yielding this trace. (1 is one trace, 2 is two stacked traces, etc.)")
|
||||
|
||||
data_use = field(
|
||||
Int16, offset=35, default=1, documentation=
|
||||
"Data use: 1 = Production, 2 = Test")
|
||||
|
||||
source_receiver_offset = field(
|
||||
Int32, offset=37, default=0, documentation=
|
||||
"Distance from center of the source point to the center of the receiver group (negative if opposite to "
|
||||
"direction in which line is shot).")
|
||||
|
||||
receiver_group_elevation = field(
|
||||
Int32, offset=41, default=0, documentation=
|
||||
"Receiver group elevation (all elevations above the Vertical datum are positive and below are negative). The "
|
||||
"elevation_scalar applies to this value.")
|
||||
|
||||
surface_elevation_at_source = field(
|
||||
Int32, offset=45, default=0, documentation=
|
||||
"Surface elevation at source. The elevation_scalar applies to this value.")
|
||||
|
||||
source_depth_below_surface = field(
|
||||
Int32, offset=49, default=0, documentation=
|
||||
"Source depth below surface (a positive number). The elevation_scalar applies to this value.")
|
||||
|
||||
datum_elevation_at_receiver_group = field(
|
||||
Int32, offset=53, default=0, documentation=
|
||||
"Source depth below surface (a positive number). The elevation_scalar applies to this value.")
|
||||
|
||||
datum_elevation_at_source = field(
|
||||
Int32, offset=57, default=0, documentation=
|
||||
"Datum elevation at source. The elevation_scalar applies to this value.")
|
||||
|
||||
water_depth_at_source = field(
|
||||
Int32, offset=61, default=0, documentation=
|
||||
"Water depth at source. The elevation_scalar applies to this value.")
|
||||
|
||||
water_depth_at_group = field(
|
||||
Int32, offset=65, default=0, documentation=
|
||||
"Water depth at group. The elevation_scalar applies to this value."
|
||||
)
|
||||
|
||||
elevation_scalar = field(
|
||||
Int16, offset=69, default=1, documentation=
|
||||
"Scalar to be applied to the elevations and depths specified in: receiver_group_elevation, "
|
||||
"surface_elevation_at_source, source_depth_below_surface, datum_elevation_at_receiver_group, "
|
||||
"datum_elevation_at_source, water_depth_at_source and water_depth_at_group, to give the real value. "
|
||||
"Scalar = 1, +10, +100, +1000, or +10,000. If positive, scalar is used as a multiplier; if negative, scalar is "
|
||||
"used as a divisor."
|
||||
)
|
||||
|
||||
xy_scalar = field(
|
||||
Int16, offset=71, default=1, documentation=
|
||||
"Scalar to be applied to all coordinates specified in source_x, source_y, group_x, group_y, cdp_x and cdp_y to "
|
||||
"give the real value. Scalar = 1, +10, +100, +1000, or +10,000. If positive, scalar is used as a multiplier; "
|
||||
"if negative, scalar is used as divisor."
|
||||
)
|
||||
|
||||
source_x = field(
|
||||
Int32, offset=73, default=0, documentation=
|
||||
"Source coordinate - X. The xy_scalar applies to this value. The coordinate reference system should be "
|
||||
"identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, "
|
||||
"decimal degrees or DMS, the X values represent longitude. A positive value designates east of Greenwich "
|
||||
"Meridian and a negative value designates west."
|
||||
)
|
||||
|
||||
source_y = field(
|
||||
Int32, offset=77, default=0, documentation=
|
||||
"Source coordinate - Y. The xy_scalar applies to this value. The coordinate reference system should be "
|
||||
"identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, "
|
||||
"decimal degrees or DMS, the Y values represent latitude. A positive value designates north of the equator and "
|
||||
"a negative value designates south."
|
||||
)
|
||||
|
||||
group_x = field(
|
||||
Int32, offset=81, default=0, documentation=
|
||||
"Group coordinate - X. The xy_scalar applies to this value. The coordinate reference system should be "
|
||||
"identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, "
|
||||
"decimal degrees or DMS, the X values represent longitude. A positive value designates east of Greenwich "
|
||||
"Meridian and a negative value designates west."
|
||||
)
|
||||
|
||||
group_y = field(
|
||||
Int32, offset=85, default=0, documentation=
|
||||
"Source coordinate - Y. The xy_scalar applies to this value. The coordinate reference system should be "
|
||||
"identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, "
|
||||
"decimal degrees or DMS, the Y values represent latitude. A positive value designates north of the equator and "
|
||||
"a negative value designates south."
|
||||
)
|
||||
|
||||
coordinate_units = field(
|
||||
Int16, offset=89, default=0, documentation=
|
||||
"Coordinate units: 1 = Length (meters or feet), 2 = Seconds of arc, 3 = Decimal degrees, 4 = Degrees, minutes, "
|
||||
"seconds (DMS). Note: To encode ±DDDMMSS bytes this value equals ±DDD*104 + MM*102 + SS with xy_scalar set to "
|
||||
"1; To encode ±DDDMMSS.ss this value equals ±DDD*106 + MM*104 + SS*102 with xy_scalar set to -100."
|
||||
)
|
||||
|
||||
weathering_velocity = field(
|
||||
Int16, offset=91, default=0, documentation=
|
||||
"Weathering velocity. (ft/s or m/s as specified in Binary File Header bytes 3255- 3256)" # TODO
|
||||
)
|
||||
|
||||
subweathering_velocity = field(
|
||||
Int16, offset=93, default=0, documentation=
|
||||
"Subweathering velocity. (ft/s or m/s as specified in Binary File Header bytes 3255-3256)" # TODO
|
||||
)
|
||||
|
||||
uphole_time_at_source = field(
|
||||
Int16, offset=95, default=0, documentation=
|
||||
"Uphole time at source in milliseconds. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
uphole_time_at_group = field(
|
||||
Int16, offset=97, default=0, documentation=
|
||||
"Uphole time at group in milliseconds. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
source_static_correction = field(
|
||||
Int16, offset=99, default=0, documentation=
|
||||
"Source static correction in milliseconds. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
group_static_correction = field(
|
||||
Int16, offset=101, default=0, documentation=
|
||||
"Group static correction in milliseconds. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
total_static = field(
|
||||
Int16, offset=103, default=0, documentation=
|
||||
"Total static applied in milliseconds. (Zero if no static has been applied). "
|
||||
"The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
lag_time_a = field(
|
||||
Int16, offset=105, default=0, documentation=
|
||||
"Lag time A. Time in milliseconds between end of 240-byte trace identification header and time break. "
|
||||
"The value is positive if time break occurs after the end of header; negative if time break occurs before the "
|
||||
"end of header. Time break is defined as the initiation pulse that may be recorded on an auxiliary trace or as "
|
||||
"otherwise specified by the recording system. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
lag_time_b = field(
|
||||
Int16, offset=107, default=0, documentation=
|
||||
"Lag Time B. Time in milliseconds between time break and the initiation time of the energy source. May be "
|
||||
"positive or negative. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
delay_recording_time = field(
|
||||
Int16, offset=109, default=0, documentation=
|
||||
"Delay recording time — Time in milliseconds between initiation time of energy source and the time when "
|
||||
"recording of data samples begins. In SEG Y rev 0 this entry was intended for deep-water work if data "
|
||||
"recording does not start at zero time. The entry can be negative to accommodate negative start times (i.e. "
|
||||
"data recorded before time zero, presumably as a result of static application to the data trace). If a "
|
||||
"non-zero value (negative or positive) is recorded in this entry, a comment to that effect should appear in "
|
||||
"the Textual File Header. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
mute_start_time = field(
|
||||
Int16, offset=111, default=0, documentation=
|
||||
"Mute time — Start time in milliseconds. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
mute_end_time = field(
|
||||
Int16, offset=113, default=0, documentation=
|
||||
"Mute time — End time in milliseconds. The time_scalar applies to this value."
|
||||
)
|
||||
|
||||
num_samples = field(
|
||||
Int16, offset=115, default=0, documentation=
|
||||
"Number of samples in this trace. Highly recommended for all types of data."
|
||||
)
|
||||
|
||||
sample_interval = field(
|
||||
Int16, offset=117, default=0, documentation=
|
||||
"Sample interval in microseconds (μs) for this trace. The number of bytes in a trace record must be consistent "
|
||||
"with the number of samples written in the trace header. This is important for all recording media; but it is "
|
||||
"particularly crucial for the correct processing of SEG Y data in disk files (see Appendix C). "
|
||||
"If the fixed length trace flag in bytes 3503-3504 of the Binary File Header is set, the sample interval and " #TODO: field names instead of byte offsets
|
||||
"number of samples in every trace in the SEG Y file must be the same as the values recorded in the Binary File "
|
||||
"Header. If the fixed length trace flag is not set, the sample interval and number of samples may vary from "
|
||||
"trace to trace. Highly recommended for all types of data."
|
||||
)
|
||||
|
||||
gain_type_of_field_instruments = field(
|
||||
Int16, offset=119, default=0, documentation=
|
||||
"Gain type of field instruments. 1 = fixed, 2 = binary, 3 = floating point, 4 ... N = optional use."
|
||||
)
|
||||
|
||||
instrument_gain_constant = field(
|
||||
Int16, offset=121, default=0, documentation=
|
||||
"Instrument gain constant (dB)."
|
||||
)
|
||||
|
||||
instrument_initial_gain = field(
|
||||
Int16, offset=123, default=0, documentation=
|
||||
"Instrument early or initial gain (dB)."
|
||||
)
|
||||
|
||||
correlated = field(
|
||||
Int16, offset=125, default=1, documentation=
|
||||
"Correlated: 1 = no, 2 = yes."
|
||||
)
|
||||
|
||||
sweep_frequency_at_start = field(
|
||||
Int16, offset=127, default=0, documentation=
|
||||
"Sweep frequency at start (Hz)."
|
||||
)
|
||||
|
||||
sweep_frequency_at_end = field(
|
||||
Int16, offset=129, default=0, documentation=
|
||||
"Sweep frequency at end (Hz)."
|
||||
)
|
||||
|
||||
sweep_length = field(
|
||||
Int16, offset=131, default=0, documentation=
|
||||
"Sweep length in milliseconds."
|
||||
)
|
||||
|
||||
sweep_type = field(
|
||||
Int16, offset=133, default=0, documentation=
|
||||
"Sweep type: 1 = linear, 2 = parabolic, 3 = exponential 4 = other."
|
||||
)
|
||||
sweep_trace_taper_length_at_start = field(
|
||||
Int16, offset=135, default=0, documentation=
|
||||
"Sweep trace taper length at start in milliseconds."
|
||||
)
|
||||
|
||||
sweep_trace_taper_length_at_end = field(
|
||||
Int16, offset=137, default=0, documentation=
|
||||
"Sweep trace taper length at end in milliseconds."
|
||||
)
|
||||
|
||||
taper_type = field(
|
||||
Int16, offset=139, default=0, documentation=
|
||||
"Taper type: 1 = linear, 2 = cos2, 3 = other"
|
||||
)
|
||||
|
||||
alias_filter_frequency = field(
|
||||
Int16, offset=141, default=0, documentation=
|
||||
"Alias filter frequency (Hz), if used."
|
||||
)
|
||||
|
||||
alias_filter_slope = field(
|
||||
Int16, offset=143, default=0, documentation=
|
||||
"Alias filter slope (dB/octave)."
|
||||
)
|
||||
|
||||
notch_filter_frequency = field(
|
||||
Int16, offset=145, default=0, documentation=
|
||||
"Notch filter frequency (Hz), if used."
|
||||
)
|
||||
|
||||
notch_filter_slope = field(
|
||||
Int16, offset=147, default=0, documentation=
|
||||
"Notch filter slope (dB/octave)."
|
||||
)
|
||||
|
||||
low_cut_frequency = field(
|
||||
Int16, offset=149, default=0, documentation=
|
||||
"Low-cut frequency (Hz), if used."
|
||||
)
|
||||
|
||||
high_cut_frequency = field(
|
||||
Int16, offset=151, default=0, documentation=
|
||||
"High-cut frequency (Hz), if used."
|
||||
)
|
||||
|
||||
low_cut_slope = field(
|
||||
Int16, offset=153, default=0, documentation=
|
||||
"Low-cut slope (dB/octave)."
|
||||
)
|
||||
|
||||
high_cut_slope = field(
|
||||
Int16, offset=155, default=0, documentation=
|
||||
"High-cut slope (dB/octave)."
|
||||
)
|
||||
|
||||
year_recorded = field(
|
||||
Int16, offset=157, default=0, documentation=
|
||||
"Year data recorded. The 1975 standard is unclear as to whether this should be recorded as a 2-digit or a "
|
||||
"4-digit year and both have been used. For SEG Y revisions beyond rev 0, the year should be recorded as the "
|
||||
"complete 4-digit Gregorian calendar year (i.e. the year 2001 should be recorded as 2001 in base 10 "
|
||||
"(7D1 in base16))."
|
||||
)
|
||||
|
||||
day_of_year = field(
|
||||
Int16, offset=159, default=0, documentation=
|
||||
"Day of year. (Julian day for GMT and UTC time basis)."
|
||||
)
|
||||
|
||||
hour_of_day = field(
|
||||
Int16, offset=161, default=0, documentation=
|
||||
"Hour of day. (24 hour clock)."
|
||||
)
|
||||
|
||||
minute_of_hour = field(
|
||||
Int16, offset=163, default=0, documentation=
|
||||
"Minute of hour."
|
||||
)
|
||||
|
||||
second_of_minute = field(
|
||||
Int16, offset=165, default=0, documentation=
|
||||
"Second of minute."
|
||||
)
|
||||
|
||||
time_basis_code = field(
|
||||
Int16, offset=167, default=0, documentation=
|
||||
"Time basis code. 1 = Local, 2 = GMT (Greenwich Mean Time), 3 = Other, should be explained in a user defined "
|
||||
"stanza in the Extended Textual File Header, 4 = UTC (Coordinated Universal Time)."
|
||||
)
|
||||
|
||||
trace_weighting_factor = field(
|
||||
Int16, offset=169, default=0, documentation=
|
||||
"Trace weighting factor. Defined as 2**-N volts for the least significant bit. (N = 0, 1, ..., 32767)."
|
||||
)
|
||||
|
||||
geophone_group_num_roll_switch_position_one = field(
|
||||
Int16, offset=171, default=0, documentation=
|
||||
"Geophone group number of roll switch position one."
|
||||
)
|
||||
|
||||
geophone_group_num_first_trace_original_field = field(
|
||||
Int16, offset=173, default=0, documentation=
|
||||
"Geophone group number of trace number one within original field record."
|
||||
)
|
||||
|
||||
geophone_group_num_last_trace_original_field = field(
|
||||
Int16, offset=175, default=0, documentation=
|
||||
"Geophone group number of last trace within original field record."
|
||||
)
|
||||
|
||||
gap_size = field(
|
||||
Int16, offset=177, default=0, documentation=
|
||||
"Gap size. (Total number of groups dropped)."
|
||||
)
|
||||
|
||||
over_travel = field(
|
||||
Int16, offset=179, default=0, documentation=
|
||||
"Over travel associated with taper at beginning or end of line. 1 = down (or behind) 2 = up (or ahead)"
|
||||
)
|
||||
|
||||
|
||||
class TraceHeaderRev1(TraceHeaderRev0, metaclass=FormatMeta):
|
||||
|
||||
cdp_x = field(
|
||||
Int32, offset=181, default=0, documentation=
|
||||
"X coordinate of ensemble (CDP) position of this trace. "
|
||||
"The coordinate reference system should be identified through an extended header Location Data stanza. "
|
||||
"The xy_scalar field applies to this value."
|
||||
)
|
||||
|
||||
cdp_y = field(
|
||||
Int32, offset=185, default=0, documentation=
|
||||
"Y coordinate of ensemble (CDP) position of this trace. "
|
||||
"The coordinate reference system should be identified through an extended header Location Data stanza. "
|
||||
"The xy_scalar field applies to this value."
|
||||
)
|
||||
|
||||
inline_number = field(
|
||||
Int32, offset=189, default=0, documentation=
|
||||
"In-line number for 3-D poststack data. If one in-line per SEG Y file is "
|
||||
"being recorded, this value should be the same for all traces in the file and the same value will be recorded "
|
||||
"in bytes 3205-3208 of the Binary File Header." # TODO: replace bytes with field name
|
||||
)
|
||||
|
||||
crossline_number = field(
|
||||
Int32, offset=193, default=0, documentation=
|
||||
"Cross-line number for 3-D poststack data. This will typically be the same "
|
||||
"value as the ensemble (CDP) number in Trace Header ensemble_num field, but this does not have to be the case."
|
||||
)
|
||||
|
||||
shotpoint_number = field(
|
||||
Int32, offset=197, default=0, documentation=
|
||||
"Shotpoint number. This is probably only applicable to 2-D poststack data. Note that it is assumed that the "
|
||||
"shotpoint number refers to the source location nearest to the ensemble (CDP) location for a particular trace. "
|
||||
"If this is not the case, there should be a comment in the Textual File Header explaining what the shotpoint "
|
||||
"number actually refers to."
|
||||
)
|
||||
|
||||
shotpoint_scalar = field(
|
||||
Int16, offset=201, default=0, documentation=
|
||||
"Scalar to be applied to the shotpoint number in trace header field shotpoint_number to give the real value. "
|
||||
"If positive, scalar is used as a multiplier; if negative as a divisor; if zero the shotpoint number is not "
|
||||
"scaled (i.e. it is an integer. A typical value will be -10, allowing shotpoint numbers with one decimal digit "
|
||||
"to the right of the decimal point)."
|
||||
)
|
||||
|
||||
trace_unit = field(
|
||||
Int16, offset=203, default=0, documentation=
|
||||
"Trace value measurement unit: "
|
||||
"-1 = Other (should be described in Data Sample Measurement Units Stanza)"
|
||||
"0 = Unknown, "
|
||||
"1 = Pascal (Pa), "
|
||||
"2 = Volts (v), "
|
||||
"3 = Millivolts (mV), "
|
||||
"4 = Amperes (A), "
|
||||
"5 = Meters (m), "
|
||||
"6 = Meters per second (m/s), "
|
||||
"7 = Meters per second squared (m/s^2), "
|
||||
"8 = Newton (N), "
|
||||
"9 = Watt (W)"
|
||||
)
|
||||
|
||||
transduction_constant_mantissa = field(
|
||||
Int32, offset=205, default=0, documentation=
|
||||
"Transduction Constant mantissa. The mantissa of the multiplicative constant used to convert the Data Trace "
|
||||
"samples to the Transduction Units (specified in Trace Header transduction_units field)."
|
||||
"The mantissa of the constant is encoded as a four-byte, two's complement integer. The constant value is given"
|
||||
"by transduction_constant_mantissa * 10**transduction_constant_exponent."
|
||||
)
|
||||
|
||||
transduction_constant_exponent = field(
|
||||
Int16, offset=209, default=0, documentation=
|
||||
"Transduction Constant exponent. The base 10 exponent of the multiplicative constant used to convert the Data "
|
||||
"Trace samples to the Transduction Units (specified in Trace Header transduction_units field). The constant "
|
||||
"value is given by transduction_constant_mantissa * 10**transduction_constant_exponent."
|
||||
)
|
||||
|
||||
transduction_units = field(
|
||||
Int16, offset=211, default=0, documentation=
|
||||
"Transduction Units. The unit of measurement of the Data Trace samples after they have been multiplied by the "
|
||||
"Transduction Constant specified in Trace Header fields transduction_constant_mantissa and "
|
||||
"transduction_constant_exponent. "
|
||||
"-1 = Other (should be described in Data Sample Measurement Unit stanza), "
|
||||
"0 = Unknown, "
|
||||
"1 = Pascal (Pa) ,"
|
||||
"2 = Volts (v), "
|
||||
"3 = Millivolts (mV), "
|
||||
"4 = Amperes (A), "
|
||||
"5 = Meters (m), "
|
||||
"6 = Meters per second (m/s), "
|
||||
"7 = Meters per second squared (m/s2), "
|
||||
"8 = Newton (N), "
|
||||
"9 = Watt (W)"
|
||||
)
|
||||
|
||||
device_trace_identifier = field(
|
||||
Int16, offset=213, default=0, documentation=
|
||||
"Device/Trace Identifier — The unit number or id number of the device associated with the Data Trace "
|
||||
"(i.e. 4368 for vibrator serial number 4368 or 20316 for gun 16 on string 3 on vessel 2). This field allows "
|
||||
"traces to be associated across trace ensembles independently of the trace number (ensemble_trace_num field)."
|
||||
)
|
||||
|
||||
time_scalar = field(
|
||||
Int16, offset=215, default=0, documentation=
|
||||
"Scalar to be applied to times specified in Trace Header fields uphole_time_at_source, uphole_time_at_group,"
|
||||
"source_static_correction, group_static_correction, total_static, lag_time_a, lag_time_b, "
|
||||
"delay_recording_time, mute_start_time, mute_end_time to give the true time value in milliseconds. "
|
||||
"Scalar = 1, +10, +100, +1000, or +10,000. If positive, scalar is used as a multiplier; "
|
||||
"if negative, scalar is used as divisor. A value of zero is assumed to be a scalar value of 1."
|
||||
)
|
||||
|
||||
source_type = field(
|
||||
Int16, offset=217, default=0, documentation=
|
||||
"Source Type/Orientation — Defines the type and the orientation of the energy source. The terms vertical, "
|
||||
"cross-line and in-line refer to the three axes of an orthogonal coordinate system. The absolute azimuthal "
|
||||
"orientation of the coordinate system axes can be defined in the Bin Grid Definition Stanza."
|
||||
"-1 to -n = Other (should be described in Source Type/Orientation stanza), "
|
||||
"0 = Unknown, "
|
||||
"1 = Vibratory - Vertical orientation, "
|
||||
"2 = Vibratory - Cross-line orientation, "
|
||||
"3 = Vibratory - In-line orientation, "
|
||||
"4 = Impulsive - Vertical orientation, "
|
||||
"5 = Impulsive - Cross-line orientation, "
|
||||
"6 = Impulsive - In-line orientation, "
|
||||
"7 = Distributed Impulsive - Vertical orientation, "
|
||||
"8 = Distributed Impulsive - Cross-line orientation, "
|
||||
"9 = Distributed Impulsive - In-line orientation, "
|
||||
)
|
||||
|
||||
source_energy_direction = field(
|
||||
Int32, offset=219, default=0, documentation= # TODO: This is six bytes. What is the format?
|
||||
"Source Energy Direction with respect to the source orientation. The positive orientation direction is "
|
||||
"defined in Bytes 217-218 of the Trace Header. The energy direction is encoded in tenths of degrees "
|
||||
"(i.e. 347.8° is encoded as 3478)."
|
||||
)
|
||||
|
||||
source_measurement_mantissa = field(
|
||||
Int32, offset=225, default=0, documentation=
|
||||
"Source Measurement mantissa. Describes the source effort used to generate the trace. The measurement can be "
|
||||
"simple, qualitative measurements such as the total weight of explosive used or the peak air gun pressure or "
|
||||
"the number of vibrators times the sweep duration. Although these simple measurements are acceptable, it is "
|
||||
"preferable to use true measurement units of energy or work. The constant is encoded as a four-byte, two's "
|
||||
"complement integer (source_measurement_mantissa) and a two-byte, two's complement integer "
|
||||
"source_measurement_exponent) which is the power of ten exponent "
|
||||
"(i.e. source_measurement_mantissa * 10**source_measurement_exponent)."
|
||||
)
|
||||
|
||||
source_measurement_exponent = field(
|
||||
Int16, offset=229, default=0, documentation=
|
||||
"Source Measurement exponent. Describes the source effort used to generate the trace. The measurement can be "
|
||||
"simple, qualitative measurements such as the total weight of explosive used or the peak air gun pressure or "
|
||||
"the number of vibrators times the sweep duration. Although these simple measurements are acceptable, it is "
|
||||
"preferable to use true measurement units of energy or work. The constant is encoded as a four-byte, two's "
|
||||
"complement integer (source_measurement_mantissa) and a two-byte, two's complement integer "
|
||||
"source_measurement_exponent) which is the power of ten exponent "
|
||||
"(i.e. source_measurement_mantissa * 10**source_measurement_exponent)."
|
||||
)
|
||||
|
||||
source_measurement_unit = field(
|
||||
Int16, offset=231, default=0, documentation=
|
||||
"Source Measurement Unit. The unit used for the source measurement."
|
||||
"-1 = Other (should be described in Source Measurement Unit stanza), "
|
||||
"0 = Unknown, "
|
||||
"1 = Joule (J), "
|
||||
"2 = Kilowatt (kW), "
|
||||
"3 = Pascal (Pa), "
|
||||
"4 = Bar (Bar), "
|
||||
"4 = Bar-meter (Bar-m), "
|
||||
"5 = Newton (N), "
|
||||
"6 = Kilograms (kg)"
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
class Int16(int):
|
||||
|
||||
MINIMUM = -32768
|
||||
MAXIMUM = 32767
|
||||
SIZE = 2
|
||||
SEG_Y_TYPE = 'int16'
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
instance = super().__new__(cls, *args, **kwargs)
|
||||
if not (Int16.MINIMUM <= instance <= Int16.MAXIMUM):
|
||||
raise ValueError("{} value {!r} outside range {} to {}".format(cls.__name__, instance,
|
||||
cls.MINIMUM, cls.MAXIMUM))
|
||||
return instance
|
||||
|
||||
|
||||
class Int32(int):
|
||||
|
||||
MINIMUM = -2147483648
|
||||
MAXIMUM = 2147483647
|
||||
SIZE = 4
|
||||
SEG_Y_TYPE = 'int32'
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
instance = super().__new__(cls, *args, **kwargs)
|
||||
if not (Int32.MINIMUM <= instance <= Int32.MAXIMUM):
|
||||
raise ValueError("{} value {!r} outside range {} to {}".format(cls.__name__, instance,
|
||||
cls.MINIMUM, cls.MAXIMUM))
|
||||
return instance
|
||||
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
import hashlib
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
|
||||
from collections.abc import Set
|
||||
from itertools import (islice, cycle, tee, chain, repeat)
|
||||
|
||||
from segpy.sorted_set import SortedFrozenSet
|
||||
|
||||
UNKNOWN_FILENAME = '<unknown>'
|
||||
|
||||
NATIVE_ENDIANNESS = '<' if sys.byteorder == 'little' else '>'
|
||||
|
||||
EMPTY_BYTE_STRING = b''
|
||||
|
||||
UNSET = object()
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
"""Pairwise iteration.
|
||||
|
||||
Args:
|
||||
iterable: An iterable series.
|
||||
|
||||
Returns:
|
||||
An iterator over 2-tuples. If the iterable contains fewer than two
|
||||
items the result series is empty.
|
||||
"""
|
||||
a, b = tee(iterable)
|
||||
next(b)
|
||||
yield from zip(a, b)
|
||||
|
||||
|
||||
def batched(iterable, batch_size, padding=UNSET):
|
||||
"""Batch an iterable series into equal sized batches.
|
||||
|
||||
Args:
|
||||
iterable: The series to be batched.
|
||||
|
||||
batch_size: The size of the batch. Must be at least one.
|
||||
|
||||
padding: Optional value used to pad the final batch to batch_size. If
|
||||
omitted, the final batch may be smaller than batch_size.
|
||||
|
||||
Yields:
|
||||
A series of lists, each containing batch_size items from iterable.
|
||||
|
||||
Raises:
|
||||
ValueError: If batch_size is less than one.
|
||||
"""
|
||||
if batch_size < 1:
|
||||
raise ValueError("Batch size {} is not at least one.".format(batch_size))
|
||||
|
||||
pending = []
|
||||
|
||||
for item in iterable:
|
||||
pending.append(item)
|
||||
if len(pending) == batch_size:
|
||||
batch = pending
|
||||
pending = []
|
||||
yield batch
|
||||
|
||||
num_left_over = len(pending)
|
||||
if num_left_over > 0:
|
||||
if padding is not UNSET:
|
||||
pending.extend([padding] * (batch_size - num_left_over))
|
||||
yield pending
|
||||
|
||||
|
||||
def pad(iterable, padding=None, size=None):
|
||||
if size is None:
|
||||
return chain(iterable, repeat(padding))
|
||||
return islice(pad(iterable, padding), size)
|
||||
|
||||
|
||||
def complementary_intervals(intervals, start=None, stop=None):
|
||||
"""Compute a complementary set of intervals which alternate with given intervals to form a contiguous range.
|
||||
|
||||
Given,
|
||||
|
||||
Start Stop
|
||||
[-----) [-----) [----)
|
||||
|
||||
produces,
|
||||
|
||||
[--) [----) [-) [---)
|
||||
|
||||
Args:
|
||||
intervals: An sequence of at least one existing slices or ranges. The type of the first interval (slice or
|
||||
range) is used as the result type.
|
||||
start: An optional start index, defaults to the start of the first slice.
|
||||
stop: An optional one-beyond-the-end index, defaults to the stop attribute of the last slice.
|
||||
|
||||
Yields:
|
||||
A complementary series of slices which alternate with the supplied slices. The number of returned
|
||||
slices will always be len(slices) + 1 since both leading and trailing slices will always be returned.
|
||||
Note the some of the returned slices may be 'empty' (having zero length).
|
||||
"""
|
||||
if len(intervals) < 1:
|
||||
raise ValueError("intervals must contain at least one interval (slice or range) object")
|
||||
|
||||
interval_type = type(intervals[0])
|
||||
|
||||
if start is None:
|
||||
start = intervals[0].start
|
||||
|
||||
if stop is None:
|
||||
stop = intervals[-1].stop
|
||||
|
||||
index = start
|
||||
for s in intervals:
|
||||
yield interval_type(index, s.start)
|
||||
index = s.stop
|
||||
|
||||
yield interval_type(index, stop)
|
||||
|
||||
def intervals_are_contiguous(intervals):
|
||||
"""Determine whether a series of intervals are contiguous.
|
||||
|
||||
Args:
|
||||
intervals: An iterable series of intervals where each interval is either
|
||||
a range or slice object.
|
||||
|
||||
Returns:
|
||||
True if the intervals are in order, contiguous and non-overlapping,
|
||||
otherwise False.
|
||||
"""
|
||||
|
||||
for a, b in pairwise(intervals):
|
||||
if a.stop != b.start:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def intervals_partially_overlap(interval_a, interval_b):
|
||||
"""Determine whether two intervals partially overlap.
|
||||
|
||||
Args:
|
||||
interval_a: A range or slice object.
|
||||
interval_b: A range or slice object.
|
||||
|
||||
Returns:
|
||||
True if interval_a partially overlaps interval_b, otherwise False if the intervals
|
||||
are either disjoint or exactly coincident.
|
||||
"""
|
||||
if interval_a == interval_b:
|
||||
return False
|
||||
|
||||
if interval_a.start <= interval_b.start:
|
||||
first_interval = interval_a
|
||||
second_interval = interval_b
|
||||
else:
|
||||
first_interval = interval_b
|
||||
second_interval = interval_a
|
||||
|
||||
return second_interval.start < first_interval.stop
|
||||
|
||||
|
||||
def roundrobin(*iterables):
|
||||
"""Take items from each iterable in turn until all iterables are exhausted.
|
||||
|
||||
roundrobin('ABC', 'D', 'EF') --> A D E B F C
|
||||
"""
|
||||
# Recipe credited to George Sakkis
|
||||
pending = len(iterables)
|
||||
nexts = cycle(iter(it).__next__ for it in iterables)
|
||||
while pending:
|
||||
try:
|
||||
for n in nexts:
|
||||
yield n()
|
||||
except StopIteration:
|
||||
pending -= 1
|
||||
nexts = cycle(islice(nexts, pending))
|
||||
|
||||
|
||||
def contains_duplicates(sorted_iterable):
|
||||
"""Determine in an iterable series contains duplicates.
|
||||
|
||||
Args:
|
||||
sorted_iterable: Any iterable series which must be sorted in either
|
||||
ascending or descending order.
|
||||
|
||||
Returns:
|
||||
True if sorted_iterable contains duplicates, otherwise False.
|
||||
"""
|
||||
for a, b in pairwise(sorted_iterable):
|
||||
if a == b:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def measure_stride(iterable):
|
||||
"""Determine whether successive numeric items differ by a constant amount.
|
||||
|
||||
Args:
|
||||
iterable: An iterable series of numeric values.
|
||||
|
||||
Returns:
|
||||
The difference between successive values (e.g. item[1] - item[0]) if
|
||||
that difference is the same between all successive pairs, otherwise
|
||||
None.
|
||||
"""
|
||||
stride = None
|
||||
for a, b in pairwise(iterable):
|
||||
new_stride = b - a
|
||||
if stride is None:
|
||||
stride = new_stride
|
||||
elif stride != new_stride:
|
||||
return None
|
||||
return stride
|
||||
|
||||
|
||||
def minmax(iterable):
|
||||
"""Return the minimum and maximum of an iterable series.
|
||||
|
||||
This function requires only a single pass over the data.
|
||||
|
||||
Args:
|
||||
iterable: An iterable series for which to determine the minimum and
|
||||
maximum values.
|
||||
|
||||
Returns:
|
||||
A 2-tuple containing the minimum and maximum values.
|
||||
"""
|
||||
iterator = iter(iterable)
|
||||
try:
|
||||
first = next(iterator)
|
||||
except StopIteration:
|
||||
raise ValueError("minmax() arg is an empty iterable series")
|
||||
minimum = first
|
||||
maximum = first
|
||||
for item in iterator:
|
||||
minimum = min(minimum, item)
|
||||
maximum = max(maximum, item)
|
||||
return minimum, maximum
|
||||
|
||||
|
||||
def file_length(fh):
|
||||
"""Determine the length of a file-like object in bytes.
|
||||
|
||||
Args:
|
||||
fh: A seekable file-like-object.
|
||||
|
||||
Returns:
|
||||
An integer length in bytes.
|
||||
"""
|
||||
pos = fh.tell()
|
||||
try:
|
||||
fh.seek(0, os.SEEK_END)
|
||||
return fh.tell()
|
||||
finally:
|
||||
fh.seek(pos, os.SEEK_SET)
|
||||
|
||||
|
||||
def filename_from_handle(fh):
|
||||
"""Determine the name of the file underlying a file-like object.
|
||||
|
||||
Args:
|
||||
fh: A file-like object.
|
||||
|
||||
Returns:
|
||||
A string containing the file name, or UNKNOWN_FILENAME if it could not
|
||||
be determined.
|
||||
"""
|
||||
try:
|
||||
return fh.name
|
||||
except AttributeError:
|
||||
return UNKNOWN_FILENAME
|
||||
|
||||
|
||||
def now_millis():
|
||||
millis = int(round(time.time() * 1000))
|
||||
return millis
|
||||
|
||||
|
||||
def round_up(integer, multiple):
|
||||
"""Round up to the nearest multiple"""
|
||||
return integer if integer % multiple == 0 else integer + multiple - integer % multiple
|
||||
|
||||
|
||||
def underscores_to_camelcase(s):
|
||||
"""Convert text_in_this_style to TextInThisStyle."""
|
||||
return ''.join(w.capitalize() for w in s.split('_'))
|
||||
|
||||
|
||||
def first_sentence(s):
|
||||
sentence, stop, _ = s.partition('.')
|
||||
return sentence + stop
|
||||
|
||||
|
||||
def lower_first(s):
|
||||
"""Lower case the first character of a string."""
|
||||
return s[:1].lower() + s[1:]
|
||||
|
||||
|
||||
def almost_equal(x, y, epsilon):
|
||||
max_xy_one = max(1.0, abs(x), abs(y))
|
||||
e = epsilon * max_xy_one
|
||||
delta = abs(x - y)
|
||||
return delta <= e
|
||||
|
||||
|
||||
def ensure_contains(collection, item):
|
||||
return collection if item in collection else conjoin(collection, item)
|
||||
|
||||
|
||||
def conjoin(collection, item):
|
||||
return collection + type(collection)((item,))
|
||||
|
||||
|
||||
def is_magic_name(name):
|
||||
return len(name) > 4 and name.startswith('__') and name.endswith('__')
|
||||
|
||||
|
||||
def super_class(cls):
|
||||
"""Return the next class in the MRO of cls."""
|
||||
mro = cls.mro()
|
||||
assert len(mro) > 0
|
||||
if len(mro) == 1:
|
||||
assert mro[0] is object
|
||||
return object
|
||||
return mro[1]
|
||||
|
||||
|
||||
def flatten(sequence_of_sequences):
|
||||
return chain.from_iterable(sequence_of_sequences)
|
||||
|
||||
|
||||
def four_bytes(byte_str):
|
||||
a, b, c, d = byte_str[:4]
|
||||
return a, b, c, d
|
||||
|
||||
|
||||
def single_item_range(item):
|
||||
"""Construct a range object which generates a single value.
|
||||
"""
|
||||
return range(item, item + 1)
|
||||
|
||||
|
||||
def make_sorted_distinct_sequence(iterable):
|
||||
"""Create a sorted immutable sequence from an iterable series.
|
||||
|
||||
Args:
|
||||
iterable: An iterable series of comparable values.
|
||||
|
||||
Returns:
|
||||
An immutable collection which supports the Sized, Iterable,
|
||||
Container and Sequence protocols.
|
||||
"""
|
||||
if isinstance(iterable, range):
|
||||
if iterable.step > 0:
|
||||
return iterable
|
||||
else:
|
||||
return reversed(iterable)
|
||||
sorted_set = SortedFrozenSet(iterable)
|
||||
if len(sorted_set) == 1:
|
||||
return single_item_range(sorted_set[0])
|
||||
stride = measure_stride(sorted_set)
|
||||
if stride is not None:
|
||||
start = sorted_set[0]
|
||||
stop = sorted_set[-1] + stride
|
||||
return range(start, stop, stride)
|
||||
return sorted_set
|
||||
|
||||
|
||||
def hash_for_file(fh, *args):
|
||||
"""Compute the SHA1 hash for file combined with any stringified additional args.
|
||||
|
||||
The resulting hash is based on both the contents and length of the supplied file-
|
||||
like object.
|
||||
|
||||
fh: A file-like object opened in binary mode.
|
||||
|
||||
*args: The stringified values of ny additional arguments with be combined
|
||||
with the file data used to compute the hash.
|
||||
|
||||
Returns:
|
||||
A string containing the hexadecimal digest.
|
||||
"""
|
||||
# TODO: Use decorator to reset file pointer
|
||||
block_size=512*128
|
||||
sha1 = hashlib.sha1()
|
||||
fh.seek(0)
|
||||
for chunk in iter(lambda: fh.read(block_size), EMPTY_BYTE_STRING):
|
||||
sha1.update(chunk)
|
||||
length = fh.tell()
|
||||
length_as_bytes = length.to_bytes((length.bit_length() // 8) + 1, byteorder='little')
|
||||
sha1.update(length_as_bytes)
|
||||
fh.seek(0)
|
||||
for arg in args:
|
||||
encoded_arg = repr(arg).encode('utf8')
|
||||
sha1.update(encoded_arg)
|
||||
digest = sha1.hexdigest()
|
||||
return digest
|
||||
|
||||
|
||||
def is_range_superset_of_range(superset_range, subset_range):
|
||||
"""Are all the elements of
|
||||
|
||||
"""
|
||||
if subset_range.start not in superset_range:
|
||||
return False
|
||||
if subset_range.step % superset_range.step != 0:
|
||||
return False
|
||||
if subset_range[-1] > superset_range[-1]:
|
||||
return False
|
||||
assert set(subset_range).issubset(set(superset_range))
|
||||
return True
|
||||
|
||||
|
||||
def is_superset(superset, subset):
|
||||
"""A more general version of set.issuperset that is smart enough to work with ranges."""
|
||||
if isinstance(subset, range) and isinstance(superset, range):
|
||||
return is_range_superset_of_range(superset, subset)
|
||||
if isinstance(superset, range):
|
||||
return all(item in superset for item in subset)
|
||||
if isinstance(superset, set):
|
||||
return superset.issuperset(subset)
|
||||
if isinstance(subset, set):
|
||||
return subset.issubset(superset)
|
||||
return set(superset).issuperset(subset)
|
||||
|
||||
|
||||
def ensure_superset(superset, subset):
|
||||
"""Ensure that one collection is a subset of another.
|
||||
|
||||
Args:
|
||||
all_items: A sequence containing all items.
|
||||
|
||||
subset: Subset must either be a collection the elements of which are a subset of
|
||||
all_items, or a slice object, in which case the subset items will be sliced
|
||||
from all_items.
|
||||
Returns:
|
||||
A sorted, distinct collection which is a subset of all_items.
|
||||
|
||||
Raises:
|
||||
ValueError: If the items in subset are not a subset of the items in all_items.
|
||||
"""
|
||||
if subset is None:
|
||||
return superset
|
||||
elif isinstance(subset, slice):
|
||||
return superset[subset]
|
||||
else:
|
||||
subset = make_sorted_distinct_sequence(subset)
|
||||
if not is_superset(superset, subset):
|
||||
raise ValueError("subset_or_slice {!r} is not a subset of all_items {!r}"
|
||||
.format(subset, superset))
|
||||
return subset
|
||||
|
||||
def true(*args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def collect_attributes(derived_class, base_class=object, predicate=None):
|
||||
"""
|
||||
|
||||
Args:
|
||||
derived_class: The class at which to start searching.
|
||||
base_class: The class at which to stop searching
|
||||
predicate: A predicate which accepts
|
||||
|
||||
Returns:
|
||||
A generator of items containing the (class, attribute_name)
|
||||
"""
|
||||
# TODO: Consider using the inspect module to do this
|
||||
if predicate is None:
|
||||
predicate = true
|
||||
|
||||
for cls in derived_class.__mro__:
|
||||
for key, value in vars(cls).items():
|
||||
if predicate(key, value):
|
||||
yield cls, key, value
|
||||
if cls is base_class:
|
||||
break
|
||||
|
||||
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
from segpy.encoding import ASCII, is_supported_encoding, UnsupportedEncodingError
|
||||
from segpy.packer import make_header_packer
|
||||
from segpy.trace_header import TraceHeaderRev1
|
||||
from segpy.util import file_length, filename_from_handle, hash_for_file
|
||||
from segpy.toolkit import (extract_revision,
|
||||
bytes_per_sample,
|
||||
read_binary_reel_header,
|
||||
read_textual_reel_header,
|
||||
read_extended_textual_headers,
|
||||
catalog_traces,
|
||||
catalog_fixed_length_traces,
|
||||
write_textual_reel_header,
|
||||
write_binary_reel_header,
|
||||
write_trace_header, write_trace_samples,
|
||||
write_extended_textual_headers,
|
||||
guess_textual_header_encoding,
|
||||
read_trace_header,
|
||||
REEL_HEADER_NUM_BYTES,
|
||||
TRACE_HEADER_NUM_BYTES,
|
||||
TEXTUAL_HEADER_NUM_BYTES)
|
||||
from segpy.reader import SegYReader, SegYReader2D, SegYReader3D, _locate_cache_file, _load_reader_from_cache, _save_reader_to_cache
|
||||
|
||||
def create_writer(fh, encoding=None, trace_header_format=TraceHeaderRev1, endian='>', progress=None, cache_directory=None, fast=False):
|
||||
"""Create a SegYWriter (or one of its subclasses) based on performing
|
||||
a scan of SEG Y data.
|
||||
|
||||
This function is the preferred method for creating SegYWriter
|
||||
objects. It reads basic header information and attempts to build
|
||||
indexes for traces, CDP numbers (for 2D surveys), and inline and
|
||||
cross line co-ordinates (for 3D surveys) to facilitate subsequent
|
||||
random-access to traces.
|
||||
|
||||
Args:
|
||||
fh: A file-like-object open in binary mode positioned such
|
||||
that the beginning of the reel header will be the next
|
||||
byte to be read. For disk-based SEG Y files, this is the
|
||||
beginning of the file.
|
||||
|
||||
encoding: An optional text encoding for the textual headers. If
|
||||
None (the default) a heuristic will be used to guess the
|
||||
header encoding.
|
||||
|
||||
trace_header_format: An optional class defining the layout of the
|
||||
trace header. Defaults to TraceHeaderRev1.
|
||||
|
||||
endian: '>' for big-endian data (the standard and default), '<'
|
||||
for little-endian (non-standard)
|
||||
|
||||
progress: A unary callable which will be passed a number
|
||||
between zero and one indicating the progress made. If
|
||||
provided, this callback will be invoked at least once with
|
||||
an argument equal to one.
|
||||
|
||||
cache_directory: The directory for the cache file. Relative paths
|
||||
are interpreted as being relative to the directory containing
|
||||
the SEG Y file. Absolute paths are used as is. If
|
||||
cache_directory is None, caching is disabled.
|
||||
|
||||
fast: Boolean flag to try a quick fixed length catalog before inline or
|
||||
CDP catalogs.
|
||||
|
||||
Raises:
|
||||
ValueError: ``fh`` is unsuitable for some reason, such as not
|
||||
being open, not being seekable, not being in
|
||||
binary mode, or being too short.
|
||||
|
||||
Returns:
|
||||
A SegYWriter object. Depending on the exact type of the
|
||||
SegYWriter returned different capabilities may be
|
||||
available. Inspect the returned object to determine these
|
||||
capabilities, or be prepared for capabilities not defined in
|
||||
the SegYWriter base class to be unavailable. The underlying
|
||||
file-like object must remain open for the duration of use of
|
||||
the returned reader object. It is the caller's responsibility
|
||||
to close the underlying file.
|
||||
|
||||
Example:
|
||||
|
||||
with open('my_seismic_data.sgy', 'rb') as fh:
|
||||
reader = create_reader(fh)
|
||||
print(reader.num_traces())
|
||||
|
||||
"""
|
||||
if hasattr(fh, 'encoding') and fh.encoding is not None:
|
||||
raise TypeError(
|
||||
"SegYWriter must be provided with a binary mode file object")
|
||||
|
||||
if not fh.seekable():
|
||||
raise TypeError(
|
||||
"SegYWriter must be provided with a seekable file object")
|
||||
|
||||
if fh.closed:
|
||||
raise ValueError(
|
||||
"SegYWriter must be provided with an open file object")
|
||||
|
||||
num_file_bytes = file_length(fh)
|
||||
if num_file_bytes < REEL_HEADER_NUM_BYTES:
|
||||
raise ValueError(
|
||||
"SEG Y file {!r} of {} bytes is too short".format(
|
||||
filename_from_handle(fh),
|
||||
num_file_bytes))
|
||||
|
||||
if endian not in ('<', '>'):
|
||||
raise ValueError("Unrecognised endian value {!r}".format(endian))
|
||||
|
||||
reader = None
|
||||
cache_file_path = None
|
||||
|
||||
if cache_directory is not None:
|
||||
sha1 = hash_for_file(fh, encoding, trace_header_format, endian)
|
||||
seg_y_path = filename_from_handle(fh)
|
||||
cache_file_path = _locate_cache_file(seg_y_path, cache_directory, sha1)
|
||||
if cache_file_path is not None:
|
||||
reader = _load_reader_from_cache(cache_file_path, seg_y_path)
|
||||
|
||||
if reader is None:
|
||||
reader = _make_writer(fh, encoding, trace_header_format, endian, progress, fast=fast)
|
||||
if cache_directory is not None:
|
||||
_save_reader_to_cache(reader, cache_file_path)
|
||||
|
||||
return reader
|
||||
|
||||
def _make_writer(fh, encoding, trace_header_format, endian, progress, fast=False):
|
||||
if encoding is None:
|
||||
encoding = guess_textual_header_encoding(fh)
|
||||
if encoding is None:
|
||||
encoding = ASCII
|
||||
textual_reel_header = read_textual_reel_header(fh, encoding)
|
||||
binary_reel_header = read_binary_reel_header(fh, endian)
|
||||
extended_textual_header = read_extended_textual_headers(fh, binary_reel_header, encoding)
|
||||
revision = extract_revision(binary_reel_header)
|
||||
bps = bytes_per_sample(binary_reel_header, revision)
|
||||
if fast:
|
||||
try:
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_fixed_length_traces(fh, binary_reel_header, trace_header_format,endian, progress)
|
||||
except:
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_traces(fh, bps, trace_header_format,endian, progress)
|
||||
else:
|
||||
try:
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_traces(fh, bps, trace_header_format,endian, progress)
|
||||
except:
|
||||
fh.seek(REEL_HEADER_NUM_BYTES)
|
||||
trace_offset_catalog, trace_length_catalog, cdp_catalog, line_catalog = catalog_fixed_length_traces(fh, binary_reel_header, trace_header_format,endian, progress)
|
||||
|
||||
|
||||
if line_catalog is not None:
|
||||
return SegYWriter3D(fh, textual_reel_header, binary_reel_header, extended_textual_header, trace_offset_catalog,
|
||||
trace_length_catalog, line_catalog, trace_header_format, encoding, endian)
|
||||
if cdp_catalog is not None:
|
||||
return SegYWriter2D(fh, textual_reel_header, binary_reel_header, extended_textual_header, trace_offset_catalog,
|
||||
trace_length_catalog, cdp_catalog, trace_header_format, encoding, endian)
|
||||
|
||||
return SegYWriter(fh, textual_reel_header, binary_reel_header, extended_textual_header, trace_offset_catalog,
|
||||
trace_length_catalog, trace_header_format, encoding, endian)
|
||||
|
||||
class SegYWriter(SegYReader):
|
||||
"""
|
||||
Mixin that extends SegyReader with Writing capabilities
|
||||
"""
|
||||
|
||||
def trace_position(self,trace_index):
|
||||
if not (0 <= trace_index < self.num_traces()):
|
||||
raise ValueError("Trace index out of range.")
|
||||
return self._trace_offset_catalog[trace_index]
|
||||
|
||||
def write_trace_header(self,trace_index,trace_header,force=True):
|
||||
"""
|
||||
Write a trace header in place
|
||||
"""
|
||||
pos=self.trace_position(trace_index)
|
||||
try:
|
||||
read_trace_header(self._fh, self._trace_header_packer, pos=pos)
|
||||
except Exception as e:
|
||||
print ("Could not read a trace header from trace_index={}, skipping writting. Pass force=False to force. Exception: {}".format(trace_index),e)
|
||||
else:
|
||||
write_trace_header(self._fh,trace_header,self._trace_header_packer,pos)
|
||||
|
||||
def write_binary_reel_header(self,binary_reel_header):
|
||||
self._fh.seek(REEL_HEADER_NUM_BYTES)
|
||||
write_binary_reel_header(self._fh, binary_reel_header, self.endian)
|
||||
|
||||
def write_textual_reel_header(self,textual_reel_header):
|
||||
self._fh.seek(0)
|
||||
write_textual_reel_header(self._fh, textual_reel_header, self.encoding)
|
||||
|
||||
def write_extended_textual_headers(self,extended_textual_header):
|
||||
self._fh.seek(TEXTUAL_HEADER_NUM_BYTES)
|
||||
write_extended_textual_headers(self._fh, extended_textual_header, self.encoding)
|
||||
|
||||
def write_trace_samples(self,trace_index,samples):
|
||||
num_samples_in_trace = self.num_trace_samples(trace_index)
|
||||
start_pos = (self.trace_position(trace_index) + TRACE_HEADER_NUM_BYTES)
|
||||
if not num_samples_in_trace==len(samples):
|
||||
raise ValueError(
|
||||
"Length of samples {} does not fit in trace size {}".format(len(samples),num_samples_in_trace))
|
||||
|
||||
write_trace_samples(self._fh, samples, self.data_sample_format, pos=start_pos, endian='>')
|
||||
|
||||
|
||||
class SegYWriter2D(SegYReader2D,SegYWriter):
|
||||
pass
|
||||
|
||||
|
||||
class SegYWriter3D(SegYReader3D,SegYWriter):
|
||||
pass
|
||||
|
||||
def main(argv=None):
|
||||
import sys
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
class ProgressBar(object):
|
||||
|
||||
def __init__(self, num_chars, character='.'):
|
||||
self._num_chars = num_chars
|
||||
self._character = character
|
||||
self._ratchet = 0
|
||||
|
||||
def __call__(self, proportion):
|
||||
existing = self._num_marks(self._ratchet)
|
||||
required = self._num_marks(proportion)
|
||||
print(self._character * (required - existing), end='')
|
||||
self._ratchet = proportion
|
||||
|
||||
def _num_marks(self, p):
|
||||
return int(round(p * self._num_chars))
|
||||
|
||||
filename = argv[0]
|
||||
|
||||
with open(filename, 'r+b') as segy_file:
|
||||
segy_reader = create_writer(segy_file, progress=ProgressBar(30))
|
||||
|
||||
trace_header = segy_reader.trace_header(0)
|
||||
trace_header.shotpoint_scalar=trace_header.shotpoint_scalar
|
||||
segy_reader.write_trace_header(0,trace_header)
|
||||
|
||||
binary_reel_header = segy_reader.binary_reel_header
|
||||
binary_reel_header.num_samples=binary_reel_header.num_samples
|
||||
segy_reader.write_binary_reel_header(binary_reel_header)
|
||||
|
||||
trace_samples=segy_reader.trace_samples(0)
|
||||
segy_reader.write_trace_samples(0,trace_samples)
|
||||
|
||||
with open(filename, 'rb') as segy_file:
|
||||
segy_reader = create_writer(segy_file, progress=ProgressBar(30))
|
||||
print()
|
||||
print("Filename: ", segy_reader.filename)
|
||||
print("SEG Y revision: ", segy_reader.revision)
|
||||
print("Number of traces: ", segy_reader.num_traces())
|
||||
print("Data format: ",
|
||||
segy_reader.data_sample_format_description)
|
||||
print("Dimensionality: ", segy_reader.dimensionality)
|
||||
|
||||
try:
|
||||
print("Number of CDPs: ", segy_reader.num_cdps())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
print("Number of inlines: ", segy_reader.num_inlines())
|
||||
print("Number of crosslines: ", segy_reader.num_xlines())
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
print("=== BEGIN TEXTUAL REEL HEADER ===")
|
||||
for line in segy_reader.textual_reel_header:
|
||||
print(line[3:])
|
||||
print("=== END TEXTUAL REEL HEADER ===")
|
||||
print()
|
||||
print("=== BEGIN EXTENDED TEXTUAL HEADER ===")
|
||||
print(segy_reader.extended_textual_header)
|
||||
print("=== END EXTENDED TEXTUAL_HEADER ===")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,207 +0,0 @@
|
||||
"""
|
||||
A python module for reading/writing/manipuating
|
||||
SEG-Y formatted filed
|
||||
|
||||
segy.segypy_version : The version of SegyPY
|
||||
segy.segypy_verbose : A,punt of verbose information to the screen.
|
||||
segy.getValue : Get a value from a binary string
|
||||
|
||||
"""
|
||||
#
|
||||
# segypy : A Python module for reading and writing SEG-Y formatted data
|
||||
#
|
||||
# (C) Thomas Mejer Hansen, 2005
|
||||
|
||||
import struct
|
||||
|
||||
# SOME GLOBAL PARAMETERS
|
||||
segypy_version=0.1
|
||||
segypy_verbose=10;
|
||||
|
||||
#endian='>' # Big Endian
|
||||
#endian='<' # Little Endian
|
||||
#endian='=' # Native
|
||||
|
||||
|
||||
l_long = struct.calcsize('l')
|
||||
l_ulong = struct.calcsize('L')
|
||||
l_short = struct.calcsize('h')
|
||||
l_ushort = struct.calcsize('H')
|
||||
l_char = struct.calcsize('c')
|
||||
l_uchar = struct.calcsize('B')
|
||||
l_float = struct.calcsize('f')
|
||||
|
||||
def readSegy(filename):
|
||||
"""
|
||||
getSegyHeader
|
||||
"""
|
||||
from pylab import *
|
||||
data = open(filename).read()
|
||||
|
||||
filesize=len(data)
|
||||
vtxt = 'Length of data ; ',filesize
|
||||
printverbose(vtxt,2)
|
||||
|
||||
SH=getSegyHeader(filename)
|
||||
|
||||
ntraces = (filesize-3600)/(SH['ns']*4+240)
|
||||
printverbose(vtxt,2)
|
||||
|
||||
vtxt = "readSegy : ntraces=",ntraces,"nsamples=",SH['ns']
|
||||
printverbose(vtxt,2)
|
||||
|
||||
index=3600
|
||||
|
||||
|
||||
# ntraces=500;
|
||||
Data = zeros((SH['ns'],ntraces))
|
||||
|
||||
printverbose("readSegy : reading data",2)
|
||||
|
||||
for itrace in range(1,ntraces,1):
|
||||
i=itrace
|
||||
# print "Reading trace ",itrace," of ",ntraces
|
||||
SegyTraceHeader,SegyTraceData=getSegyTrace(SH,itrace)
|
||||
|
||||
for iss in range(1,SH['ns'],1):
|
||||
Data[iss-1][itrace-1]=SegyTraceData[0][iss-1]
|
||||
|
||||
printverbose("readSegy : read data",2)
|
||||
|
||||
if (segypy_verbose>2):
|
||||
imshow(Data)
|
||||
title('pymat test')
|
||||
grid(True)
|
||||
show()
|
||||
|
||||
|
||||
return Data,SH
|
||||
|
||||
def getSegyTrace(SH,itrace):
|
||||
data = open(SH["filename"]).read()
|
||||
# GET TRACE HEADER
|
||||
index=3200+(itrace-1)*(240+SH['ns']*4)
|
||||
SegyTraceHeader=[];
|
||||
#print index
|
||||
|
||||
# GET TRACE
|
||||
index=3200+(itrace-1)*(240+SH['ns']*4)+240
|
||||
SegyTraceData = getValue(data,index,'float','>',SH['ns'])
|
||||
return SegyTraceHeader,SegyTraceData
|
||||
|
||||
|
||||
def getSegyHeader(filename,offset=3200):
|
||||
|
||||
data = open(filename).read()
|
||||
|
||||
printverbose('getSegyHeader : trying to read from '+filename,1)
|
||||
|
||||
# START INDEX IN FILE
|
||||
index=offset;
|
||||
index=0;
|
||||
|
||||
SegyHeader = {'filename': filename}
|
||||
|
||||
TextualFileHeader,index = getValue(data,index,'c','>',3200);
|
||||
|
||||
SegyHeader['Job'],index = getValue(data,index,'l','>');
|
||||
SegyHeader['Line'],index = getValue(data,index,'l','>')
|
||||
SegyHeader['Reel'],index = getValue(data,index,'l','>')
|
||||
SegyHeader['DataTracePerEnsemble'],index = getValue(data,index,'h','>')
|
||||
SegyHeader['AuxiliaryTracePerEnsemble'],index = getValue(data,index,'short','>')
|
||||
SegyHeader['dt'],index = getValue(data,index,'uint16');
|
||||
SegyHeader['dtOrig'],index = getValue(data,index,'uint16');
|
||||
SegyHeader['ns'],index = getValue(data,index,'uint16');
|
||||
SegyHeader['nsOrig'],index = getValue(data,index,'uint16');
|
||||
SegyHeader['DataSampleFormat'],index = getValue(data,index,'int16');
|
||||
SegyHeader['EnsembleFold'],index = getValue(data,index,'int16');
|
||||
SegyHeader['TraceSorting'],index = getValue(data,index,'int16');
|
||||
SegyHeader['VerticalSumCode'],index = getValue(data,index,'int16');
|
||||
|
||||
SegyHeader['SweepFrequencyStart'],index = getValue(data,index,'int16');
|
||||
SegyHeader['SweepFrequencyEnd'],index = getValue(data,index,'int16');
|
||||
SegyHeader['SweepLength'],index = getValue(data,index,'int16');
|
||||
SegyHeader['SweepType'],index = getValue(data,index,'int16');
|
||||
SegyHeader['SweepChannel'],index = getValue(data,index,'int16');
|
||||
SegyHeader['SweepTaperlengthStart'],index = getValue(data,index,'int16');
|
||||
SegyHeader['SweepTaperLengthEnd'],index = getValue(data,index,'int16');
|
||||
SegyHeader['TaperType'],index = getValue(data,index,'int16');
|
||||
SegyHeader['CorrelatedDataTraces'],index = getValue(data,index,'int16');
|
||||
SegyHeader['BinaryGain'],index = getValue(data,index,'int16');
|
||||
SegyHeader['AmplitudeRecoveryMethod'],index = getValue(data,index,'int16');
|
||||
SegyHeader['MeasurementSystem'],index = getValue(data,index,'int16');
|
||||
SegyHeader['ImpulseSignalPolarity'],index = getValue(data,index,'int16');
|
||||
SegyHeader['VibratoryPolarityCode'],index = getValue(data,index,'int16');
|
||||
|
||||
index=3500;
|
||||
SegyHeader['SegyFormatRevisionNumber'],index= getValue(data,index,'uint16');
|
||||
SegyHeader['FixedLengthTraceFlag'],index=getValue(data,index,'uint16');
|
||||
SegyHeader['NumberOfExtTextualHeaders'],index= getValue(data,index,'uint16');
|
||||
|
||||
printverbose('getSegyHeader : succesfully read '+filename,1)
|
||||
|
||||
return SegyHeader
|
||||
|
||||
|
||||
|
||||
def getValue(data,index,ctype='l',endian='>',number=1):
|
||||
"""
|
||||
GET
|
||||
"""
|
||||
if (ctype=='l')|(ctype=='long'):
|
||||
size=l_long
|
||||
ctype='l'
|
||||
elif (ctype=='L')|(ctype=='ulong'):
|
||||
size=l_ulong
|
||||
ctype='L'
|
||||
elif (ctype=='h')|(ctype=='short')|(ctype=='int16'):
|
||||
size=l_short
|
||||
ctype='h'
|
||||
elif (ctype=='H')|(ctype=='ushort')|(ctype=='uint16'):
|
||||
size=l_ushort
|
||||
ctype='H'
|
||||
elif (ctype=='c')|(ctype=='char'):
|
||||
size=l_char
|
||||
ctype='c'
|
||||
elif (ctype=='B')|(ctype=='uchar'):
|
||||
size=l_uchar
|
||||
ctype='B'
|
||||
elif (ctype=='f')|(ctype=='float'):
|
||||
size=l_float
|
||||
ctype='f'
|
||||
else:
|
||||
printverbose('Bad Ctype : ' +ctype,-1)
|
||||
|
||||
cformat=endian + ctype*number
|
||||
|
||||
printverbose('cformat : ' + cformat,20)
|
||||
|
||||
index_end=index+size*number
|
||||
HeaderValue=struct.unpack(cformat, data[index:index_end])
|
||||
|
||||
str = 'getSegyHeaderValue','start=',index,' size=',size, 'number=',number,'Value=',HeaderValue,'cformat=',cformat
|
||||
printverbose(str,20)
|
||||
|
||||
if number==1:
|
||||
return HeaderValue[0], index_end
|
||||
else:
|
||||
return HeaderValue,index_end
|
||||
|
||||
|
||||
def version():
|
||||
return segypy_version
|
||||
|
||||
def print_version():
|
||||
print 'SegyPY version is ', segypy_version
|
||||
|
||||
def printverbose(txt,level):
|
||||
if level<segypy_verbose:
|
||||
print 'SegyPY',segypy_version,': ',txt
|
||||
|
||||
|
||||
|
||||
#def read(filename):
|
||||
# read_segy(filename)
|
||||
#
|
||||
#def write(filename):
|
||||
# write_segy(filename)
|
||||
@@ -0,0 +1,5 @@
|
||||
[bdist_wheel]
|
||||
# This flag says that the code is written to work on both Python 2 and Python
|
||||
# 3. If at all possible, it is good practice to do this. If you cannot, you
|
||||
# will need to generate wheels for each Python version that you support.
|
||||
universal=1
|
||||
@@ -0,0 +1,118 @@
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
from setuptools import setup, find_packages # Always prefer setuptools over distutils
|
||||
from codecs import open # To use a consistent encoding
|
||||
from os import path
|
||||
|
||||
|
||||
def read(*names, **kwargs):
|
||||
with io.open(
|
||||
os.path.join(os.path.dirname(__file__), *names),
|
||||
encoding=kwargs.get("encoding", "utf8")
|
||||
) as fp:
|
||||
return fp.read()
|
||||
|
||||
|
||||
def find_version(*file_paths):
|
||||
version_file = read(*file_paths)
|
||||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
|
||||
version_file, re.M)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
# Get the long description from the relevant file
|
||||
with open(path.join(here, 'DESCRIPTION.rst'), encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='segpy',
|
||||
|
||||
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||
# the version across setup.py and the project code, see
|
||||
# https://packaging.python.org/en/latest/single_source_version.html
|
||||
version=find_version("segpy/__init__.py"),
|
||||
|
||||
description='Transfer of seismic data to and from SEG Y files',
|
||||
long_description=long_description,
|
||||
|
||||
# The project's main homepage.
|
||||
url='https://github.com/sixty-north/segpy',
|
||||
|
||||
# Author details
|
||||
author='Sixty North AS',
|
||||
author_email='rob@sixty-north.com',
|
||||
|
||||
# Choose your license
|
||||
license='GNU Affero General Public License v3',
|
||||
|
||||
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
classifiers=[
|
||||
# How mature is this project? Common values are
|
||||
# 3 - Alpha
|
||||
# 4 - Beta
|
||||
# 5 - Production/Stable
|
||||
'Development Status :: 3 - Alpha',
|
||||
|
||||
# Indicate who your project is intended for
|
||||
'Intended Audience :: Developers',
|
||||
'Topic :: Scientific/Engineering',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
|
||||
# Pick your license as you wish (should match "license" above)
|
||||
'License :: OSI Approved :: GNU Affero General Public License v3',
|
||||
|
||||
# Specify the Python versions you support here. In particular, ensure
|
||||
# that you indicate whether you support Python 2, Python 3 or both.
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
],
|
||||
|
||||
# What does your project relate to?
|
||||
keywords='seismic geocomputing geophysics',
|
||||
|
||||
# You can just specify the packages manually here if your project is
|
||||
# simple. Or you can use find_packages().
|
||||
packages=find_packages(exclude=['contrib', 'docs', 'test*']),
|
||||
|
||||
# List run-time dependencies here. These will be installed by pip when your
|
||||
# project is installed. For an analysis of "install_requires" vs pip's
|
||||
# requirements files see:
|
||||
# https://packaging.python.org/en/latest/requirements.html
|
||||
install_requires=[],
|
||||
|
||||
# List additional groups of dependencies here (e.g. development dependencies).
|
||||
# You can install these using the following syntax, for example:
|
||||
# $ pip install -e .[dev,test]
|
||||
extras_require = {
|
||||
'dev': ['check-manifest', 'wheel'],
|
||||
'doc': ['sphinx', 'cartouche'],
|
||||
'test': ['coverage', 'hypothesis'],
|
||||
},
|
||||
|
||||
# If there are data files included in your packages that need to be
|
||||
# installed, specify them here. If using Python 2.6 or less, then these
|
||||
# have to be included in MANIFEST.in as well.
|
||||
package_data={
|
||||
},
|
||||
|
||||
# Although 'package_data' is the preferred approach, in some case you may
|
||||
# need to place data files outside of your packages.
|
||||
# see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files
|
||||
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
|
||||
data_files=[],
|
||||
|
||||
# To provide executable scripts, use entry points in preference to the
|
||||
# "scripts" keyword. Entry points provide cross-platform support and allow
|
||||
# pip to create the appropriate form of executable for the target platform.
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
from itertools import accumulate, starmap
|
||||
from hypothesis.strategies import integers, just, fixed_dictionaries, lists
|
||||
from segpy.trace_header import TraceHeaderRev0
|
||||
from segpy.util import batched
|
||||
|
||||
PRINTABLE_ASCII_RANGE = (32, 127)
|
||||
|
||||
|
||||
def multiline_ascii_encodable_text(min_num_lines, max_num_lines):
|
||||
"""A Hypothesis strategy to produce a multiline Unicode string.
|
||||
|
||||
Args:
|
||||
min_num_lines: The minimum number of lines in the produced strings.
|
||||
max_num_lines: The maximum number of lines in the produced strings.
|
||||
|
||||
Returns:
|
||||
A strategy for generating Unicode strings containing only newlines
|
||||
and characters which are encodable as printable 7-bit ASCII characters.
|
||||
"""
|
||||
|
||||
return integers(min_num_lines, max_num_lines) \
|
||||
.flatmap(lambda n: lists(integers(*PRINTABLE_ASCII_RANGE), min_size=n, max_size=n)) \
|
||||
.map(lambda xs: '\n'.join(bytes(x).decode('ascii') for x in xs))
|
||||
|
||||
|
||||
def spaced_ranges(min_num_ranges, max_num_ranges, min_interval, max_interval):
|
||||
"""A Hypothesis strategy to produce separated, non-overlapping ranges.
|
||||
|
||||
Args:
|
||||
min_num_ranges: The minimum number of ranges to produce. TODO: Correct?
|
||||
max_num_ranges: The maximum number of ranges to produce.
|
||||
min_interval: The minimum interval used for the lengths of the alternating ranges and spaces.
|
||||
max_interval: The maximum interval used for the lengths of the alternating ranges and spaces.
|
||||
"""
|
||||
return integers(min_num_ranges, max_num_ranges) \
|
||||
.map(lambda n: 2*n) \
|
||||
.flatmap(lambda n: lists(integers(min_interval, max_interval), min_size=n, max_size=n)) \
|
||||
.map(list).map(lambda lst: list(accumulate(lst))) \
|
||||
.map(lambda lst: list(batched(lst, 2))) \
|
||||
.map(lambda pairs: list(starmap(range, pairs)))
|
||||
|
||||
|
||||
def header(header_class, **kwargs):
|
||||
"""Create a strategy for producing headers of a specific class.
|
||||
|
||||
Args:
|
||||
header_class: The type of header to be produced. This class will be
|
||||
introspected to determine suitable strategies for each named
|
||||
field.
|
||||
|
||||
**kwargs: Any supplied keyword arguments can be used to fix the value
|
||||
of particular header fields.
|
||||
"""
|
||||
|
||||
field_strategies = {}
|
||||
for field_name in header_class.ordered_field_names():
|
||||
if field_name in kwargs:
|
||||
field_strategy = just(kwargs.pop(field_name))
|
||||
else:
|
||||
value_type = getattr(header_class, field_name).value_type
|
||||
field_strategy = integers(value_type.MINIMUM, value_type.MAXIMUM)
|
||||
field_strategies[field_name] = field_strategy
|
||||
|
||||
if len(kwargs) > 0:
|
||||
raise TypeError("Unrecognised binary header field names {} for {}".format(
|
||||
', '.join(kwargs.keys()),
|
||||
header_class.__name__))
|
||||
|
||||
return fixed_dictionaries(field_strategies) \
|
||||
.map(lambda kw: header_class(**kw))
|
||||
@@ -0,0 +1 @@
|
||||
hypothesis>=1.11
|
||||
@@ -0,0 +1,91 @@
|
||||
import unittest
|
||||
|
||||
from hypothesis import given, example, assume
|
||||
from hypothesis.strategies import dictionaries, just, integers, streaming, tuples
|
||||
from segpy.catalog import CatalogBuilder
|
||||
|
||||
|
||||
class TestCatalogBuilder(unittest.TestCase):
|
||||
|
||||
@given(dictionaries(integers(), integers()))
|
||||
def test_arbitrary_mapping(self, mapping):
|
||||
builder = CatalogBuilder(mapping)
|
||||
catalog = builder.create()
|
||||
shared_items = set(mapping.items()) & set(catalog.items())
|
||||
self.assertEqual(len(shared_items), len(mapping))
|
||||
|
||||
@given(dictionaries(integers(), just(42)))
|
||||
def test_constant_mapping(self, mapping):
|
||||
builder = CatalogBuilder(mapping)
|
||||
catalog = builder.create()
|
||||
shared_items = set(mapping.items()) & set(catalog.items())
|
||||
self.assertEqual(len(shared_items), len(mapping))
|
||||
|
||||
@given(start=integers(),
|
||||
num=integers(0, 10000),
|
||||
step=integers(-10000, 10000),
|
||||
value=integers())
|
||||
def test_regular_constant_mapping(self, start, num, step, value):
|
||||
assume(step != 0)
|
||||
mapping = {key: value for key in range(start, start + num*step, step)}
|
||||
builder = CatalogBuilder(mapping)
|
||||
catalog = builder.create()
|
||||
shared_items = set(mapping.items()) & set(catalog.items())
|
||||
self.assertEqual(len(shared_items), len(mapping))
|
||||
|
||||
@given(start=integers(),
|
||||
num=integers(0, 10000),
|
||||
step=integers(-10000, 10000),
|
||||
values=streaming(integers()))
|
||||
def test_regular_mapping(self, start, num, step, values):
|
||||
assume(step != 0)
|
||||
mapping = {key: value for key, value in zip(range(start, start + num*step, step), values)}
|
||||
builder = CatalogBuilder(mapping)
|
||||
catalog = builder.create()
|
||||
shared_items = set(mapping.items()) & set(catalog.items())
|
||||
self.assertEqual(len(shared_items), len(mapping))
|
||||
|
||||
@given(num=integers(0, 10000),
|
||||
key_start=integers(),
|
||||
key_step=integers(-10000, 10000),
|
||||
value_start=integers(),
|
||||
value_step=integers(-10000, 10000))
|
||||
def test_linear_regular_mapping(self, num, key_start, key_step, value_start, value_step):
|
||||
assume(key_step != 0)
|
||||
assume(value_step != 0)
|
||||
mapping = {key: value for key, value in zip(range(key_start, key_start + num*key_step, key_step),
|
||||
range(value_start, value_start + num*value_step, value_step))}
|
||||
builder = CatalogBuilder(mapping)
|
||||
catalog = builder.create()
|
||||
shared_items = set(mapping.items()) & set(catalog.items())
|
||||
self.assertEqual(len(shared_items), len(mapping))
|
||||
|
||||
@given(dictionaries(tuples(integers(), integers()), integers()))
|
||||
def test_arbitrary_mapping(self, mapping):
|
||||
builder = CatalogBuilder(mapping)
|
||||
catalog = builder.create()
|
||||
shared_items = set(mapping.items()) & set(catalog.items())
|
||||
self.assertEqual(len(shared_items), len(mapping))
|
||||
|
||||
@given(i_start=integers(0, 10),
|
||||
i_num=integers(1, 10),
|
||||
i_step=just(1),
|
||||
j_start=integers(0, 10),
|
||||
j_num=integers(1, 10),
|
||||
j_step=just(1),
|
||||
c=integers(1, 10))
|
||||
def test_linear_regular_mapping_2d(self, i_start, i_num, i_step, j_start, j_num, j_step, c):
|
||||
assume(i_step != 0)
|
||||
assume(j_step != 0)
|
||||
|
||||
def v(i, j):
|
||||
return (i - i_start) * ((j_start + j_num*j_step) - j_start) + (j - j_start) + c
|
||||
|
||||
mapping = {(i, j): v(i, j)
|
||||
for i in range(i_start, i_start + i_num*i_step, i_step)
|
||||
for j in range(j_start, j_start + j_num*j_step, j_step)}
|
||||
|
||||
builder = CatalogBuilder(mapping)
|
||||
catalog = builder.create()
|
||||
shared_items = set(mapping.items()) & set(catalog.items())
|
||||
self.assertEqual(len(shared_items), len(mapping))
|
||||
@@ -0,0 +1,43 @@
|
||||
import unittest
|
||||
|
||||
from hypothesis import given
|
||||
from hypothesis.strategies import sampled_from, just, booleans
|
||||
|
||||
from segpy.encoding import EBCDIC, ASCII
|
||||
from segpy.toolkit import format_extended_textual_header, CARDS_PER_HEADER, END_TEXT_STANZA, CARD_LENGTH
|
||||
from test.strategies import multiline_ascii_encodable_text
|
||||
|
||||
|
||||
class TestFormatExtendedTextualHeader(unittest.TestCase):
|
||||
|
||||
@given(multiline_ascii_encodable_text(0, 100),
|
||||
sampled_from([ASCII, EBCDIC]),
|
||||
booleans())
|
||||
def test_forty_lines_per_page(self, text, encoding, include_text_stop):
|
||||
pages = format_extended_textual_header(text, encoding, include_text_stop)
|
||||
self.assertTrue(all(len(page) == CARDS_PER_HEADER for page in pages))
|
||||
|
||||
@given(multiline_ascii_encodable_text(0, 100),
|
||||
sampled_from([ASCII, EBCDIC]),
|
||||
booleans())
|
||||
def test_eighty_bytes_per_encoded_line(self, text, encoding, include_text_stop):
|
||||
pages = format_extended_textual_header(text, encoding, include_text_stop)
|
||||
self.assertTrue(all([len(line.encode(encoding)) == CARD_LENGTH for page in pages for line in page]))
|
||||
|
||||
@given(multiline_ascii_encodable_text(0, 100),
|
||||
sampled_from([ASCII, EBCDIC]),
|
||||
booleans())
|
||||
def test_lines_end_with_cr_lf(self, text, encoding, include_text_stop):
|
||||
pages = format_extended_textual_header(text, encoding, include_text_stop)
|
||||
self.assertTrue(all([line.endswith('\r\n') for page in pages for line in page]))
|
||||
|
||||
@given(multiline_ascii_encodable_text(0, 100),
|
||||
sampled_from([ASCII, EBCDIC]),
|
||||
just(True))
|
||||
def test_end_text_stanza_present(self, text, encoding, include_text_stop):
|
||||
pages = format_extended_textual_header(text, encoding, include_text_stop)
|
||||
self.assertTrue(pages[-1][0].startswith(END_TEXT_STANZA))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,361 @@
|
||||
import math
|
||||
from math import trunc
|
||||
import unittest
|
||||
|
||||
from hypothesis import given, assume
|
||||
from hypothesis.strategies import integers, floats
|
||||
|
||||
from segpy.ibm_float import (ieee2ibm, ibm2ieee, MAX_IBM_FLOAT, SMALLEST_POSITIVE_NORMAL_IBM_FLOAT,
|
||||
LARGEST_NEGATIVE_NORMAL_IBM_FLOAT, MIN_IBM_FLOAT, IBMFloat, EPSILON_IBM_FLOAT,
|
||||
MAX_EXACT_INTEGER_IBM_FLOAT, MIN_EXACT_INTEGER_IBM_FLOAT, EXPONENT_BIAS)
|
||||
from segpy.util import almost_equal
|
||||
|
||||
|
||||
class Ibm2Ieee(unittest.TestCase):
|
||||
|
||||
def test_zero(self):
|
||||
self.assertEqual(ibm2ieee(b'\0\0\0\0'), 0.0)
|
||||
|
||||
def test_positive_half(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0b11000000, 0x80, 0x00, 0x00))), -0.5)
|
||||
|
||||
def test_negative_half(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0b01000000, 0x80, 0x00, 0x00))), 0.5)
|
||||
|
||||
def test_one(self):
|
||||
self.assertEqual(ibm2ieee(b'\x41\x10\x00\x00'), 1.0)
|
||||
|
||||
def test_negative_118_625(self):
|
||||
# Example taken from Wikipedia http://en.wikipedia.org/wiki/IBM_Floating_Point_Architecture
|
||||
self.assertEqual(ibm2ieee(bytes((0b11000010, 0b01110110, 0b10100000, 0b00000000))), -118.625)
|
||||
|
||||
def test_largest_representable_number(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0b01111111, 0b11111111, 0b11111111, 0b11111111))), MAX_IBM_FLOAT)
|
||||
|
||||
def test_smallest_positive_normalised_number(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0b00000000, 0b00010000, 0b00000000, 0b00000000))), SMALLEST_POSITIVE_NORMAL_IBM_FLOAT)
|
||||
|
||||
def test_largest_negative_normalised_number(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0b10000000, 0b00010000, 0b00000000, 0b00000000))), LARGEST_NEGATIVE_NORMAL_IBM_FLOAT)
|
||||
|
||||
def test_smallest_representable_number(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0b11111111, 0b11111111, 0b11111111, 0b11111111))), MIN_IBM_FLOAT)
|
||||
|
||||
def test_error_1(self):
|
||||
self.assertEqual(ibm2ieee(bytes((196, 74, 194, 143))), -19138.55859375)
|
||||
|
||||
def test_error_2(self):
|
||||
self.assertEqual(ibm2ieee(bytes((191, 128, 0, 0))), -0.03125)
|
||||
|
||||
def test_subnormal(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0x00, 0x00, 0x00, 0x20))), 1.6472184286297693e-83)
|
||||
|
||||
def test_subnormal_is_subnormal(self):
|
||||
self.assertTrue(0 < ibm2ieee(bytes((0x00, 0x00, 0x00, 0x20))) < SMALLEST_POSITIVE_NORMAL_IBM_FLOAT)
|
||||
|
||||
def test_subnormal_smallest_subnormal(self):
|
||||
self.assertEqual(ibm2ieee(bytes((0x00, 0x00, 0x00, 0x01))), 5.147557589468029e-85)
|
||||
|
||||
|
||||
class Ieee2Ibm(unittest.TestCase):
|
||||
|
||||
def test_zero(self):
|
||||
self.assertEqual(ieee2ibm(0.0), b'\0\0\0\0')
|
||||
|
||||
def test_positive_half(self):
|
||||
self.assertEqual(ieee2ibm(-0.5), bytes((0b11000000, 0x80, 0x00, 0x00)))
|
||||
|
||||
def test_negative_half(self):
|
||||
self.assertEqual(ieee2ibm(0.5), bytes((0b01000000, 0x80, 0x00, 0x00)))
|
||||
|
||||
def test_one(self):
|
||||
self.assertEqual(ieee2ibm(1.0), b'\x41\x10\x00\x00')
|
||||
|
||||
def test_negative_118_625(self):
|
||||
# Example taken from Wikipedia http://en.wikipedia.org/wiki/IBM_Floating_Point_Architecture
|
||||
self.assertEqual(ieee2ibm(-118.625), bytes((0b11000010, 0b01110110, 0b10100000, 0b00000000)))
|
||||
|
||||
def test_0_1(self):
|
||||
# Note, this is different from the Wikipedia example, because the Wikipedia example does
|
||||
# round to nearest, and our routine does round to zero
|
||||
self.assertEqual(ieee2ibm(0.1), bytes((0b01000000, 0b00011001, 0b10011001, 0b10011001)))
|
||||
|
||||
def test_subnormal(self):
|
||||
self.assertEqual(ieee2ibm(1.6472184286297693e-83), bytes((0x00, 0x00, 0x00, 0x20)))
|
||||
|
||||
def test_smallest_subnormal(self):
|
||||
self.assertEqual(ieee2ibm(5.147557589468029e-85), bytes((0x00, 0x00, 0x00, 0x01)))
|
||||
|
||||
def test_too_small_subnormal(self):
|
||||
with self.assertRaises(FloatingPointError):
|
||||
ieee2ibm(1e-86)
|
||||
|
||||
def test_nan(self):
|
||||
with self.assertRaises(ValueError):
|
||||
ieee2ibm(float('nan'))
|
||||
|
||||
def test_inf(self):
|
||||
with self.assertRaises(ValueError):
|
||||
ieee2ibm(float('inf'))
|
||||
|
||||
def test_too_large(self):
|
||||
with self.assertRaises(OverflowError):
|
||||
ieee2ibm(MAX_IBM_FLOAT * 10)
|
||||
|
||||
def test_too_small(self):
|
||||
with self.assertRaises(OverflowError):
|
||||
ieee2ibm(MIN_IBM_FLOAT * 10)
|
||||
|
||||
|
||||
class Ibm2IeeeRoundtrip(unittest.TestCase):
|
||||
|
||||
def test_zero(self):
|
||||
ibm_start = b'\0\0\0\0'
|
||||
f = ibm2ieee(ibm_start)
|
||||
ibm_result = ieee2ibm(f)
|
||||
self.assertEqual(ibm_start, ibm_result)
|
||||
|
||||
def test_positive_half(self):
|
||||
ibm_start = bytes((0b11000000, 0x80, 0x00, 0x00))
|
||||
f = ibm2ieee(ibm_start)
|
||||
ibm_result = ieee2ibm(f)
|
||||
self.assertEqual(ibm_start, ibm_result)
|
||||
|
||||
def test_negative_half(self):
|
||||
ibm_start = bytes((0b01000000, 0x80, 0x00, 0x00))
|
||||
f = ibm2ieee(ibm_start)
|
||||
ibm_result = ieee2ibm(f)
|
||||
self.assertEqual(ibm_start, ibm_result)
|
||||
|
||||
def test_one(self):
|
||||
ibm_start = b'\x41\x10\x00\x00'
|
||||
f = ibm2ieee(ibm_start)
|
||||
ibm_result = ieee2ibm(f)
|
||||
self.assertEqual(ibm_start, ibm_result)
|
||||
|
||||
def test_subnormal(self):
|
||||
ibm_start = bytes((0x00, 0x00, 0x00, 0x20))
|
||||
f = ibm2ieee(ibm_start)
|
||||
ibm_result = ieee2ibm(f)
|
||||
self.assertEqual(ibm_start, ibm_result)
|
||||
|
||||
|
||||
class TestIBMFloat(unittest.TestCase):
|
||||
|
||||
def test_zero_from_float(self):
|
||||
zero = IBMFloat.from_float(0.0)
|
||||
self.assertTrue(zero.is_zero())
|
||||
|
||||
def test_zero_from_bytes(self):
|
||||
zero = IBMFloat.from_bytes(b'\x00\x00\x00\x00')
|
||||
self.assertTrue(zero.is_zero())
|
||||
|
||||
def test_subnormal(self):
|
||||
ibm = IBMFloat.from_float(1.6472184286297693e-83)
|
||||
self.assertTrue(ibm.is_subnormal())
|
||||
|
||||
def test_smallest_subnormal(self):
|
||||
ibm = IBMFloat.from_float(5.147557589468029e-85)
|
||||
self.assertEqual(bytes(ibm), bytes((0x00, 0x00, 0x00, 0x01)))
|
||||
|
||||
def test_too_small_subnormal(self):
|
||||
with self.assertRaises(FloatingPointError):
|
||||
IBMFloat.from_float(1e-86)
|
||||
|
||||
def test_nan(self):
|
||||
with self.assertRaises(ValueError):
|
||||
IBMFloat.from_float(float('nan'))
|
||||
|
||||
def test_inf(self):
|
||||
with self.assertRaises(ValueError):
|
||||
IBMFloat.from_float(float('inf'))
|
||||
|
||||
def test_too_large(self):
|
||||
with self.assertRaises(OverflowError):
|
||||
IBMFloat.from_float(MAX_IBM_FLOAT * 10)
|
||||
|
||||
def test_too_small(self):
|
||||
with self.assertRaises(OverflowError):
|
||||
IBMFloat.from_float(MIN_IBM_FLOAT * 10)
|
||||
|
||||
@given(floats(MIN_IBM_FLOAT, MAX_IBM_FLOAT))
|
||||
def test_bool(self, f):
|
||||
self.assertEqual(bool(IBMFloat.from_float(f)), bool(f))
|
||||
|
||||
@given(integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255))
|
||||
def test_bytes_roundtrip(self, a, b, c, d):
|
||||
b = bytes((a, b, c, d))
|
||||
ibm = IBMFloat.from_bytes(b)
|
||||
self.assertEqual(bytes(ibm), b)
|
||||
|
||||
@given(floats(MIN_IBM_FLOAT, MAX_IBM_FLOAT))
|
||||
def test_floats_roundtrip(self, f):
|
||||
ibm = IBMFloat.from_float(f)
|
||||
self.assertTrue(almost_equal(f, float(ibm), epsilon=EPSILON_IBM_FLOAT))
|
||||
|
||||
@given(integers(0, MAX_EXACT_INTEGER_IBM_FLOAT - 1),
|
||||
floats(0.0, 1.0))
|
||||
def test_trunc_above_zero(self, i, f):
|
||||
assume(f != 1.0)
|
||||
ieee = i + f
|
||||
ibm = IBMFloat.from_float(ieee)
|
||||
self.assertEqual(trunc(ibm), i)
|
||||
|
||||
@given(integers(MIN_EXACT_INTEGER_IBM_FLOAT + 1, 0),
|
||||
floats(0.0, 1.0))
|
||||
def test_trunc_below_zero(self, i, f):
|
||||
assume(f != 1.0)
|
||||
ieee = i - f
|
||||
ibm = IBMFloat.from_float(ieee)
|
||||
self.assertEqual(trunc(ibm), i)
|
||||
|
||||
@given(integers(MIN_EXACT_INTEGER_IBM_FLOAT, MAX_EXACT_INTEGER_IBM_FLOAT - 1),
|
||||
floats(EPSILON_IBM_FLOAT, 1 - EPSILON_IBM_FLOAT))
|
||||
def test_ceil(self, i, f):
|
||||
ieee = i + f
|
||||
ibm = IBMFloat.from_float(ieee)
|
||||
self.assertEqual(math.ceil(ibm), i + 1)
|
||||
|
||||
@given(integers(MIN_EXACT_INTEGER_IBM_FLOAT, MAX_EXACT_INTEGER_IBM_FLOAT - 1),
|
||||
floats(EPSILON_IBM_FLOAT, 1 - EPSILON_IBM_FLOAT))
|
||||
def test_floor(self, i, f):
|
||||
ieee = i + f
|
||||
ibm = IBMFloat.from_float(ieee)
|
||||
self.assertEqual(math.floor(ibm), i)
|
||||
|
||||
def test_normalise_subnormal_expect_failure(self):
|
||||
# This float has an base-16 exponent of -64 (the minimum) and cannot be normalised
|
||||
ibm = IBMFloat.from_float(1.6472184286297693e-83)
|
||||
assert ibm.is_subnormal()
|
||||
with self.assertRaises(FloatingPointError):
|
||||
ibm.normalize()
|
||||
|
||||
def test_normalise_subnormal1(self):
|
||||
ibm = IBMFloat.from_bytes((0b01000000, 0b00000000, 0b11111111, 0b00000000))
|
||||
assert ibm.is_subnormal()
|
||||
normalized = ibm.normalize()
|
||||
self.assertFalse(normalized.is_subnormal())
|
||||
|
||||
def test_normalise_subnormal2(self):
|
||||
ibm = IBMFloat.from_bytes((64, 1, 0, 0))
|
||||
assert ibm.is_subnormal()
|
||||
normalized = ibm.normalize()
|
||||
self.assertFalse(normalized.is_subnormal())
|
||||
|
||||
@given(integers(128, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(4, 23))
|
||||
def test_normalise_subnormal(self, b, c, d, shift):
|
||||
mantissa = (b << 16) | (c << 8) | d
|
||||
assume(mantissa != 0)
|
||||
mantissa >>= shift
|
||||
assert mantissa != 0
|
||||
|
||||
sa = EXPONENT_BIAS
|
||||
sb = (mantissa >> 16) & 0xff
|
||||
sc = (mantissa >> 8) & 0xff
|
||||
sd = mantissa & 0xff
|
||||
|
||||
ibm = IBMFloat.from_bytes((sa, sb, sc, sd))
|
||||
assert ibm.is_subnormal()
|
||||
normalized = ibm.normalize()
|
||||
self.assertFalse(normalized.is_subnormal())
|
||||
|
||||
@given(integers(128, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(4, 23))
|
||||
def test_zero_subnormal(self, b, c, d, shift):
|
||||
mantissa = (b << 16) | (c << 8) | d
|
||||
assume(mantissa != 0)
|
||||
mantissa >>= shift
|
||||
assert mantissa != 0
|
||||
|
||||
sa = EXPONENT_BIAS
|
||||
sb = (mantissa >> 16) & 0xff
|
||||
sc = (mantissa >> 8) & 0xff
|
||||
sd = mantissa & 0xff
|
||||
|
||||
ibm = IBMFloat.from_bytes((sa, sb, sc, sd))
|
||||
assert ibm.is_subnormal()
|
||||
z = ibm.zero_subnormal()
|
||||
self.assertTrue(z.is_zero())
|
||||
|
||||
@given(integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255))
|
||||
def test_abs(self, a, b, c, d):
|
||||
ibm = IBMFloat.from_bytes((a, b, c, d))
|
||||
abs_ibm = abs(ibm)
|
||||
self.assertGreaterEqual(abs_ibm.signbit, 0)
|
||||
|
||||
@given(integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255),
|
||||
integers(0, 255))
|
||||
def test_negate_non_zero(self, a, b, c, d):
|
||||
ibm = IBMFloat.from_bytes((a, b, c, d))
|
||||
assume(not ibm.is_zero())
|
||||
negated = -ibm
|
||||
self.assertNotEqual(ibm.signbit, negated.signbit)
|
||||
|
||||
def test_negate_zero(self):
|
||||
zero = IBMFloat.from_float(0.0)
|
||||
negated = -zero
|
||||
self.assertTrue(negated.is_zero())
|
||||
|
||||
@given(floats(MIN_IBM_FLOAT, MAX_IBM_FLOAT))
|
||||
def test_signbit(self, f):
|
||||
ltz = f < 0
|
||||
ibm = IBMFloat.from_float(f)
|
||||
self.assertEqual(ltz, ibm.signbit)
|
||||
|
||||
@given(floats(-1.0, +1.0),
|
||||
integers(-256, 255))
|
||||
def test_ldexp_frexp(self, fraction, exponent):
|
||||
try:
|
||||
ibm = IBMFloat.ldexp(fraction, exponent)
|
||||
except OverflowError:
|
||||
assume(False)
|
||||
else:
|
||||
f, e = ibm.frexp()
|
||||
self.assertTrue(almost_equal(fraction * 2**exponent, f * 2**e, epsilon=EPSILON_IBM_FLOAT))
|
||||
|
||||
@given(floats(MIN_IBM_FLOAT, MAX_IBM_FLOAT),
|
||||
floats(0.0, 1.0))
|
||||
def test_add(self, f, p):
|
||||
a = f * p
|
||||
b = f - a
|
||||
|
||||
ibm_a = IBMFloat.from_float(a)
|
||||
ibm_b = IBMFloat.from_float(b)
|
||||
ibm_c = ibm_a + ibm_b
|
||||
|
||||
ieee_a = float(ibm_a)
|
||||
ieee_b = float(ibm_b)
|
||||
ieee_c = ieee_a + ieee_b
|
||||
|
||||
self.assertTrue(almost_equal(ieee_c, ibm_c, epsilon=EPSILON_IBM_FLOAT * 4))
|
||||
|
||||
@given(floats(0, MAX_IBM_FLOAT),
|
||||
floats(0, MAX_IBM_FLOAT))
|
||||
def test_sub(self, a, b):
|
||||
ibm_a = IBMFloat.from_float(a)
|
||||
ibm_b = IBMFloat.from_float(b)
|
||||
ibm_c = ibm_a - ibm_b
|
||||
|
||||
ieee_a = float(ibm_a)
|
||||
ieee_b = float(ibm_b)
|
||||
ieee_c = ieee_a - ieee_b
|
||||
|
||||
self.assertTrue(almost_equal(ieee_c, ibm_c, epsilon=EPSILON_IBM_FLOAT))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,73 @@
|
||||
from itertools import zip_longest
|
||||
import unittest
|
||||
from hypothesis import given
|
||||
from io import BytesIO
|
||||
from hypothesis.strategies import sampled_from
|
||||
from segpy.binary_reel_header import BinaryReelHeader
|
||||
from segpy.encoding import ASCII, EBCDIC
|
||||
from segpy.header import are_equal
|
||||
|
||||
from segpy.toolkit import write_binary_reel_header, read_binary_reel_header, write_textual_reel_header, \
|
||||
read_textual_reel_header, CARDS_PER_HEADER, CARD_LENGTH
|
||||
from test.strategies import header, multiline_ascii_encodable_text
|
||||
|
||||
|
||||
class TestBinaryReelHeader(unittest.TestCase):
|
||||
|
||||
@given(header(BinaryReelHeader),
|
||||
sampled_from(['<', '>']))
|
||||
def test_roundtrip(self, write_header, endian):
|
||||
with BytesIO() as write_stream:
|
||||
write_binary_reel_header(write_stream, write_header, endian)
|
||||
written_stream = write_stream.getvalue()
|
||||
|
||||
with BytesIO(written_stream) as read_stream:
|
||||
read_header = read_binary_reel_header(read_stream, endian)
|
||||
|
||||
self.assertTrue(are_equal(write_header, read_header))
|
||||
|
||||
|
||||
class TestTextualReelHeader(unittest.TestCase):
|
||||
|
||||
@given(multiline_ascii_encodable_text(0, CARDS_PER_HEADER),
|
||||
sampled_from([ASCII, EBCDIC]))
|
||||
def test_roundtrip(self, write_header_text, encoding):
|
||||
write_header_lines = write_header_text.splitlines()
|
||||
with BytesIO() as write_stream:
|
||||
write_textual_reel_header(write_stream, write_header_lines, encoding)
|
||||
written_stream = write_stream.getvalue()
|
||||
|
||||
with BytesIO(written_stream) as read_stream:
|
||||
read_header_lines = read_textual_reel_header(read_stream, encoding)
|
||||
|
||||
for written_line, read_line in zip_longest(write_header_lines[:CARDS_PER_HEADER],
|
||||
read_header_lines,
|
||||
fillvalue=""):
|
||||
self.assertEqual(written_line[:CARD_LENGTH].rstrip().ljust(CARD_LENGTH),
|
||||
read_line)
|
||||
|
||||
@given(multiline_ascii_encodable_text(0, CARDS_PER_HEADER),
|
||||
sampled_from([ASCII, EBCDIC]))
|
||||
def test_header_num_lines(self, write_header_text, encoding):
|
||||
write_header_lines = write_header_text.splitlines()
|
||||
with BytesIO() as write_stream:
|
||||
write_textual_reel_header(write_stream, write_header_lines, encoding)
|
||||
written_stream = write_stream.getvalue()
|
||||
|
||||
with BytesIO(written_stream) as read_stream:
|
||||
read_header_lines = read_textual_reel_header(read_stream, encoding)
|
||||
|
||||
self.assertEqual(len(read_header_lines), CARDS_PER_HEADER)
|
||||
|
||||
@given(multiline_ascii_encodable_text(0, CARDS_PER_HEADER),
|
||||
sampled_from([ASCII, EBCDIC]))
|
||||
def test_header_line_length(self, write_header_text, encoding):
|
||||
write_header_lines = write_header_text.splitlines()
|
||||
with BytesIO() as write_stream:
|
||||
write_textual_reel_header(write_stream, write_header_lines, encoding)
|
||||
written_stream = write_stream.getvalue()
|
||||
|
||||
with BytesIO(written_stream) as read_stream:
|
||||
read_header_lines = read_textual_reel_header(read_stream, encoding)
|
||||
|
||||
self.assertTrue(all(len(line) == CARD_LENGTH for line in read_header_lines))
|
||||
@@ -0,0 +1,378 @@
|
||||
import unittest
|
||||
from collections.abc import (Container, Sized, Iterable, Sequence)
|
||||
|
||||
from segpy.sorted_set import SortedFrozenSet
|
||||
|
||||
|
||||
class TestConstruction(unittest.TestCase):
|
||||
|
||||
def test_empty(self):
|
||||
s = SortedFrozenSet()
|
||||
|
||||
def test_from_sequence(self):
|
||||
s = SortedFrozenSet([7, 8, 3, 1])
|
||||
|
||||
def test_with_duplicates(self):
|
||||
s = SortedFrozenSet([8, 8, 8])
|
||||
|
||||
def test_from_iterable(self):
|
||||
def gen6842():
|
||||
yield 6
|
||||
yield 8
|
||||
yield 4
|
||||
yield 2
|
||||
g = gen6842()
|
||||
s = SortedFrozenSet(g)
|
||||
|
||||
def test_default_empty(self):
|
||||
s = SortedFrozenSet()
|
||||
|
||||
|
||||
|
||||
class TestContainerProtocol(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.s = SortedFrozenSet([6, 7, 3, 9])
|
||||
|
||||
def test_positive_contained(self):
|
||||
self.assertTrue(6 in self.s)
|
||||
|
||||
def test_negative_contained(self):
|
||||
self.assertFalse(2 in self.s)
|
||||
|
||||
def test_positive_not_contained(self):
|
||||
self.assertTrue(5 not in self.s)
|
||||
|
||||
def test_negative_not_contained(self):
|
||||
self.assertFalse(9 not in self.s)
|
||||
|
||||
def test_sequence_protocol(self):
|
||||
self.assertTrue(issubclass(SortedFrozenSet, Container))
|
||||
|
||||
|
||||
class TestSizedProtocol(unittest.TestCase):
|
||||
|
||||
def test_empty(self):
|
||||
s = SortedFrozenSet()
|
||||
self.assertEqual(len(s), 0)
|
||||
|
||||
def test_one(self):
|
||||
s = SortedFrozenSet([42])
|
||||
self.assertEqual(len(s), 1)
|
||||
|
||||
def test_ten(self):
|
||||
s = SortedFrozenSet(range(10))
|
||||
self.assertEqual(len(s), 10)
|
||||
|
||||
def test_with_duplicates(self):
|
||||
s = SortedFrozenSet([5, 5, 5])
|
||||
self.assertEqual(len(s), 1)
|
||||
|
||||
def test_protocol(self):
|
||||
self.assertTrue(issubclass(SortedFrozenSet, Sized))
|
||||
|
||||
|
||||
class TestIterableProtocol(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.s = SortedFrozenSet([7, 2, 1, 1, 9])
|
||||
|
||||
def test_iter(self):
|
||||
i = iter(self.s)
|
||||
self.assertEqual(next(i), 1)
|
||||
self.assertEqual(next(i), 2)
|
||||
self.assertEqual(next(i), 7)
|
||||
self.assertEqual(next(i), 9)
|
||||
self.assertRaises(StopIteration, lambda: next(i))
|
||||
|
||||
def test_for_loop(self):
|
||||
index = 0
|
||||
expected = [1, 2, 7, 9]
|
||||
for item in self.s:
|
||||
self.assertEqual(item, expected[index])
|
||||
index += 1
|
||||
|
||||
def test_protocol(self):
|
||||
self.assertTrue(issubclass(SortedFrozenSet, Iterable))
|
||||
|
||||
class TestSequenceProtocol(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.s = SortedFrozenSet([1, 4, 9, 13, 15])
|
||||
|
||||
def test_index_zero(self):
|
||||
self.assertEqual(self.s[0], 1)
|
||||
|
||||
def test_index_four(self):
|
||||
self.assertEqual(self.s[4], 15)
|
||||
|
||||
def test_index_one_beyond_the_end(self):
|
||||
self.assertRaises(IndexError, lambda: self.s[5])
|
||||
|
||||
def test_index_minus_one(self):
|
||||
self.assertEqual(self.s[-1], 15)
|
||||
|
||||
def test_index_minus_five(self):
|
||||
self.assertEqual(self.s[-5], 1)
|
||||
|
||||
def test_index_one_before_the_beginning(self):
|
||||
self.assertRaises(IndexError, lambda: self.s[-6])
|
||||
|
||||
def test_slice_from_start(self):
|
||||
self.assertEqual(self.s[:3], SortedFrozenSet([1, 4, 9]))
|
||||
|
||||
def test_slice_to_end(self):
|
||||
self.assertEqual(self.s[3:], SortedFrozenSet([13, 15]))
|
||||
|
||||
def test_slice_empty(self):
|
||||
self.assertEqual(self.s[10:], SortedFrozenSet())
|
||||
|
||||
def test_slice_arbitrary(self):
|
||||
self.assertEqual(self.s[2:4], SortedFrozenSet([9, 13]))
|
||||
|
||||
def test_slice_full(self):
|
||||
self.assertEqual(self.s[:], self.s)
|
||||
|
||||
def test_reversed(self):
|
||||
s = SortedFrozenSet([1, 3, 5, 7])
|
||||
r = reversed(s)
|
||||
self.assertEqual(next(r), 7)
|
||||
self.assertEqual(next(r), 5)
|
||||
self.assertEqual(next(r), 3)
|
||||
self.assertEqual(next(r), 1)
|
||||
self.assertRaises(StopIteration, lambda: next(r))
|
||||
|
||||
def test_index_positive(self):
|
||||
s = SortedFrozenSet([1, 5, 8, 9])
|
||||
self.assertEqual(s.index(8), 2)
|
||||
|
||||
def test_index_negative(self):
|
||||
s = SortedFrozenSet([1, 5, 8, 9])
|
||||
self.assertRaises(ValueError, lambda: s.index(15))
|
||||
|
||||
def test_count_zero(self):
|
||||
s = SortedFrozenSet([1, 5, 7, 9])
|
||||
self.assertEqual(s.count(11), 0)
|
||||
|
||||
def test_count_one(self):
|
||||
s = SortedFrozenSet([1, 5, 7, 9])
|
||||
self.assertEqual(s.count(7), 1)
|
||||
|
||||
def test_protocol(self):
|
||||
self.assertTrue(issubclass(SortedFrozenSet, Sequence))
|
||||
|
||||
def test_concatenate_disjoint(self):
|
||||
s = SortedFrozenSet([1, 2, 3])
|
||||
t = SortedFrozenSet([4, 5, 6])
|
||||
self.assertEqual(s + t, SortedFrozenSet([1, 2, 3, 4, 5, 6]))
|
||||
|
||||
def test_concatenate_equal(self):
|
||||
s = SortedFrozenSet([2, 4, 6])
|
||||
self.assertEqual(s + s, s)
|
||||
|
||||
def test_concatenate_intersecting(self):
|
||||
s = SortedFrozenSet([1, 2, 3])
|
||||
t = SortedFrozenSet([3, 4, 5])
|
||||
self.assertEqual(s + t, SortedFrozenSet([1, 2, 3, 4, 5]))
|
||||
|
||||
def test_repetition_zero_lhs(self):
|
||||
s = SortedFrozenSet([4, 5, 6])
|
||||
self.assertEquals(0 * s, SortedFrozenSet())
|
||||
|
||||
def test_repetition_zero_rhs(self):
|
||||
s = SortedFrozenSet([4, 5, 6])
|
||||
self.assertEquals(s * 0, SortedFrozenSet())
|
||||
|
||||
def test_repetition_nonzero_lhs(self):
|
||||
s = SortedFrozenSet([4, 5, 6])
|
||||
self.assertEquals(100 * s, s)
|
||||
|
||||
def test_repetition_nonzero_rhs(self):
|
||||
s = SortedFrozenSet([4, 5, 6])
|
||||
self.assertEquals(s * 100, s)
|
||||
|
||||
class TestReprProtocol(unittest.TestCase):
|
||||
|
||||
def test_repr_empty(self):
|
||||
s = SortedFrozenSet()
|
||||
self.assertEqual(repr(s), "SortedFrozenSet()")
|
||||
|
||||
def test_repr_one(self):
|
||||
s = SortedFrozenSet([42, 40, 19])
|
||||
self.assertEqual(repr(s), "SortedFrozenSet([19, 40, 42])")
|
||||
|
||||
|
||||
class TestEqualityProtocol(unittest.TestCase):
|
||||
|
||||
def test_positive_equal(self):
|
||||
self.assertTrue(SortedFrozenSet([4, 5, 6]) == SortedFrozenSet([6, 5, 4]))
|
||||
|
||||
def test_negative_equal(self):
|
||||
self.assertFalse(SortedFrozenSet([4, 5, 6]) == SortedFrozenSet([1, 2, 3]))
|
||||
|
||||
def test_type_mismatch(self):
|
||||
self.assertFalse(SortedFrozenSet([4, 5, 6]) == [4, 5, 6])
|
||||
|
||||
def test_identical(self):
|
||||
s = SortedFrozenSet([10, 11, 12])
|
||||
self.assertTrue(s == s)
|
||||
|
||||
|
||||
class TestInequalityProtocol(unittest.TestCase):
|
||||
|
||||
def test_positive_inequal(self):
|
||||
self.assertTrue(SortedFrozenSet([4, 5, 6]) != SortedFrozenSet([1, 2, 3]))
|
||||
|
||||
def test_negative_inequal(self):
|
||||
self.assertFalse(SortedFrozenSet([4, 5, 6]) != SortedFrozenSet([6, 5, 4]))
|
||||
|
||||
def test_type_mismatch(self):
|
||||
self.assertTrue(SortedFrozenSet([1, 2, 3]) != [1, 2, 3])
|
||||
|
||||
def test_identical(self):
|
||||
s = SortedFrozenSet([10, 11, 12])
|
||||
self.assertFalse(s != s)
|
||||
|
||||
|
||||
class TestRelationalSetProtocol(unittest.TestCase):
|
||||
|
||||
def test_lt_positive(self):
|
||||
s = SortedFrozenSet({1, 2})
|
||||
t = SortedFrozenSet({1, 2, 3})
|
||||
self.assertTrue(s < t)
|
||||
|
||||
def test_lt_negative(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({1, 2, 3})
|
||||
self.assertFalse(s < t)
|
||||
|
||||
def test_le_lt_positive(self):
|
||||
s = SortedFrozenSet({1, 2})
|
||||
t = SortedFrozenSet({1, 2, 3})
|
||||
self.assertTrue(s <= t)
|
||||
|
||||
def test_le_eq_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({1, 2, 3})
|
||||
self.assertTrue(s <= t)
|
||||
|
||||
def test_le_negative(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({1, 2})
|
||||
self.assertFalse(s <= t)
|
||||
|
||||
def test_gt_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({1, 2})
|
||||
self.assertTrue(s > t)
|
||||
|
||||
def test_gt_negative(self):
|
||||
s = SortedFrozenSet({1, 2})
|
||||
t = SortedFrozenSet({1, 2, 3})
|
||||
self.assertFalse(s > t)
|
||||
|
||||
def test_ge_gt_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({1, 2})
|
||||
self.assertTrue(s > t)
|
||||
|
||||
def test_ge_eq_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({1, 2, 3})
|
||||
self.assertTrue(s >= t)
|
||||
|
||||
def test_ge_negative(self):
|
||||
s = SortedFrozenSet({1, 2})
|
||||
t = SortedFrozenSet({1, 2, 3})
|
||||
self.assertFalse(s >= t)
|
||||
|
||||
|
||||
class TestSetRelationalMethods(unittest.TestCase):
|
||||
|
||||
def test_issubset_proper_positive(self):
|
||||
s = SortedFrozenSet({1, 2})
|
||||
t = [1, 2, 3]
|
||||
self.assertTrue(s.issubset(t))
|
||||
|
||||
def test_issubset_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [1, 2, 3]
|
||||
self.assertTrue(s.issubset(t))
|
||||
|
||||
def test_issubset_negative(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [1, 2]
|
||||
self.assertFalse(s.issubset(t))
|
||||
|
||||
def test_issuperset_proper_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [1, 2]
|
||||
self.assertTrue(s.issuperset(t))
|
||||
|
||||
def test_issuperset_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [1, 2, 3]
|
||||
self.assertTrue(s.issuperset(t))
|
||||
|
||||
def test_issuperset_negative(self):
|
||||
s = SortedFrozenSet({1, 2})
|
||||
t = [1, 2, 3]
|
||||
self.assertFalse(s.issuperset(t))
|
||||
|
||||
def test_isdisjoint_positive(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [4, 5, 6]
|
||||
self.assertTrue(s.isdisjoint(t))
|
||||
|
||||
def test_isdisjoint_negative(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [3, 4, 5]
|
||||
self.assertFalse(s.isdisjoint(t))
|
||||
|
||||
|
||||
class TestOperationsSetProtocol(unittest.TestCase):
|
||||
|
||||
def test_intersection(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({2, 3, 4})
|
||||
self.assertEqual(s & t, SortedFrozenSet({2, 3}))
|
||||
|
||||
def test_union(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({2, 3, 4})
|
||||
self.assertEqual(s | t, SortedFrozenSet({1, 2, 3, 4}))
|
||||
|
||||
def test_symmetric_difference(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({2, 3, 4})
|
||||
self.assertEqual(s ^ t, SortedFrozenSet({1, 4}))
|
||||
|
||||
def test_difference(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = SortedFrozenSet({2, 3, 4})
|
||||
self.assertEqual(s - t, SortedFrozenSet({1}))
|
||||
|
||||
class TestSetOperationsMethods(unittest.TestCase):
|
||||
|
||||
def test_intersection(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [2, 3, 4]
|
||||
self.assertEqual(s.intersection(t), SortedFrozenSet({2, 3}))
|
||||
|
||||
def test_union(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [2, 3, 4]
|
||||
self.assertEqual(s.union(t), SortedFrozenSet({1, 2, 3, 4}))
|
||||
|
||||
def test_symmetric_difference(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [2, 3, 4]
|
||||
self.assertEqual(s.symmetric_difference(t), SortedFrozenSet({1, 4}))
|
||||
|
||||
def test_difference(self):
|
||||
s = SortedFrozenSet({1, 2, 3})
|
||||
t = [2, 3, 4]
|
||||
self.assertEqual(s.difference(t), SortedFrozenSet({1}))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,80 @@
|
||||
import unittest
|
||||
|
||||
from hypothesis import given, assume, example
|
||||
from hypothesis.strategies import integers, lists
|
||||
from segpy.util import batched, complementary_intervals, flatten, intervals_are_contiguous, roundrobin
|
||||
from test.strategies import spaced_ranges
|
||||
|
||||
|
||||
class TestBatched(unittest.TestCase):
|
||||
|
||||
@given(lists(integers()),
|
||||
integers(1, 1000))
|
||||
def test_batch_sizes_unpadded(self, items, batch_size):
|
||||
assume(batch_size > 0)
|
||||
batches = list(batched(items, batch_size))
|
||||
self.assertTrue(all(len(batch) == batch_size for batch in batches[:-1]))
|
||||
|
||||
@given(lists(integers()),
|
||||
integers(1, 1000))
|
||||
def test_final_batch_sizes(self, items, batch_size):
|
||||
assume(len(items) > 0)
|
||||
assume(batch_size > 0)
|
||||
batches = list(batched(items, batch_size))
|
||||
self.assertTrue(len(batches[-1]) <= batch_size)
|
||||
|
||||
@given(lists(integers()),
|
||||
integers(1, 1000),
|
||||
integers())
|
||||
def test_batch_sizes_padded(self, items, batch_size, pad):
|
||||
assume(batch_size > 0)
|
||||
batches = list(batched(items, batch_size, padding=pad))
|
||||
self.assertTrue(all(len(batch) == batch_size for batch in batches))
|
||||
|
||||
@given(lists(integers()),
|
||||
integers(1, 1000),
|
||||
integers())
|
||||
def test_pad_contents(self, items, batch_size, pad):
|
||||
assume(len(items) > 0)
|
||||
assume(0 < batch_size < 1000)
|
||||
num_left_over = len(items) % batch_size
|
||||
pad_length = batch_size - num_left_over if num_left_over != 0 else 0
|
||||
assume(pad_length != 0)
|
||||
batches = list(batched(items, batch_size, padding=pad))
|
||||
self.assertEqual(batches[-1][batch_size - pad_length:], [pad] * pad_length)
|
||||
|
||||
def test_pad(self):
|
||||
batches = list(batched([0, 0], 3, 42))
|
||||
self.assertEqual(batches[-1], [0, 0, 42])
|
||||
|
||||
|
||||
class TestComplementaryIntervals(unittest.TestCase):
|
||||
|
||||
@given(spaced_ranges(min_num_ranges=1, max_num_ranges=10,
|
||||
min_interval=0, max_interval=10))
|
||||
def test_contiguous(self, intervals):
|
||||
complements = complementary_intervals(intervals)
|
||||
interleaved = list(roundrobin(complements, intervals))
|
||||
self.assertTrue(intervals_are_contiguous(interleaved))
|
||||
|
||||
@given(spaced_ranges(min_num_ranges=1, max_num_ranges=10,
|
||||
min_interval=0, max_interval=10),
|
||||
integers(0, 10))
|
||||
def test_contiguous_with_offset_start(self, intervals, start_offset):
|
||||
first_interval_start = intervals[0].start
|
||||
start_index = first_interval_start - start_offset
|
||||
complements = list(complementary_intervals(intervals, start=start_index))
|
||||
self.assertEqual(complements[0], range(start_index, first_interval_start))
|
||||
|
||||
@given(spaced_ranges(min_num_ranges=1, max_num_ranges=10,
|
||||
min_interval=0, max_interval=10),
|
||||
integers(0, 10))
|
||||
@example(intervals=[range(0, 0)], end_offset=1)
|
||||
def test_contiguous_with_offset_end(self, intervals, end_offset):
|
||||
last_interval_end = intervals[-1].stop
|
||||
end_index = last_interval_end + end_offset
|
||||
complements = list(complementary_intervals(intervals, stop=end_index))
|
||||
self.assertEqual(complements[-1], range(last_interval_end, end_index))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
#
|
||||
#
|
||||
# testsegy.py
|
||||
#
|
||||
import struct
|
||||
import segypy
|
||||
|
||||
filename='mini.sgy'
|
||||
|
||||
data = open(filename).read()
|
||||
|
||||
l_long = struct.calcsize('l')
|
||||
l_ulong = struct.calcsize('L')
|
||||
l_short = struct.calcsize('h')
|
||||
l_ushort = struct.calcsize('H')
|
||||
|
||||
segypy_version=segypy.version()
|
||||
|
||||
print 'filename=',filename
|
||||
|
||||
|
||||
#index=3200;
|
||||
#Job,size = segypy.getValue(data,index,'l','>')
|
||||
#Job,size = segypy.getValue(data,index,'long','>')
|
||||
#index=index+size
|
||||
#Line,size = segypy.getValue(data,index,'l','>')
|
||||
#index =index+size
|
||||
#Reel,size = segypy.getValue(data,index,'l','>')
|
||||
#index=index+size
|
||||
#DataTracePerEnsemble, size = segypy.getValue(data,index,'h','>')
|
||||
#DataTracePerEnsemble, size = segypy.getValue(data,index,'short','>')
|
||||
|
||||
print "GET SEGY GEADER"
|
||||
SH=segypy.getSegyHeader(filename)
|
||||
|
||||
print SH
|
||||
#print SH['Reel']
|
||||
|
||||
print "TESTING READING OF SEGY FILE"
|
||||
segypy.readSegy(filename);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user