322 Commits
Author SHA1 Message Date
gitea f9b0d7903c Headers yield pairs, can be used like dicts 2015-12-03 10:52:02 +08:00
gitea d604e328ef Iter method for headers 2015-12-03 10:38:44 +08:00
gitea 2460cc09ae Extended SegYReader to SegYWriter for inplace writing 2015-12-03 10:38:38 +08:00
gitea fde4d3add0 Fixed length catalog is an option and a fallback 2015-12-03 10:38:33 +08:00
gitea 82e02dc7f2 Fast catalog for fixed length SEG Y 2015-12-03 10:38:27 +08:00
gitea f94301a951 If valid 2d and 3d, use 3d instead of neither 2015-12-03 10:37:55 +08:00
Robert Smallshire d29d984b58 Merge pull request #25 from DRMacIver/master
Upgrade to new Hypothesis API.
2015-09-25 21:54:40 +02:00
David R. MacIver 18434779e3 Upgrade to new Hypothesis API. 2015-09-25 15:45:57 +01:00
Robert Smallshire f851958d4c Fixes Issue #23 where the ceil() and floor() tests for IBMFloat were incorrect. 2015-09-20 10:45:09 +02:00
Robert Smallshire 91562fddfd Makes documentation consistent with API. 2015-06-15 09:57:52 +02:00
Robert Smallshire 47361906b1 Changes segpy-numpy to segpy_numpy : hyphen to an underscore in the source directectory. 2015-06-15 09:42:52 +02:00
Robert Smallshire a565f4d245 Building the initial catatogs is now about 7 times faster. 2015-06-14 20:24:13 +02:00
Robert Smallshire b053d5d5f3 Makes documentation consistent with implementation. 2015-06-14 20:22:47 +02:00
Robert Smallshire 9b80ce49de Renames SubHeader to SubFormat. 2015-06-14 20:22:05 +02:00
Robert Smallshire c7209f909a Merge branch 'ext-numpy' 2015-06-14 17:38:42 +02:00
Robert Smallshire 2c5774c0b4 Removes unnecessary code and todos 2015-06-14 17:31:45 +02:00
Robert Smallshire 965b9482da Adds extract_timeslice_3d to segpy_numpy 2015-06-14 17:30:46 +02:00
Robert Smallshire 2bee1e4f99 Adds inline/xline range support to extract_trace_header_3d. Adds extract_xline_3d 2015-06-14 17:20:29 +02:00
Robert Smallshire 9652730586 Move examples dependent on segpy-numpy to segpy-numpy/examples 2015-06-14 16:46:46 +02:00
Robert Smallshire f792029829 Use SubFormats to make extract_trace_header_3d about four times faster when only extracting a single field. 2015-06-14 16:44:16 +02:00
Robert Smallshire 480bb024be Adds a reminder to use a SubFormat when building catalogs. 2015-06-14 16:43:18 +02:00
Robert Smallshire a3ecf62a09 Adds SubFormatMeta for subsetting header formats. 2015-06-14 16:42:43 +02:00
Robert Smallshire a7d658995a Adds a true() function which always returns True. 2015-06-14 16:40:30 +02:00
Robert Smallshire 5312cca052 Adds a property to reader to retrieve the default trace header class. Makes it possible to override the trace header class when retrieving a trace header. 2015-06-14 16:39:34 +02:00
Robert Smallshire 042b415ca1 Update contributors license agreements. 2015-06-11 12:37:23 +02:00
Robert Smallshire c4703af67a Work in progress on extraction to numpy data structures. 2015-06-11 12:32:00 +02:00
Robert Smallshire e5d409eb93 Refactors extract_inline_3d 2015-06-08 23:39:09 +02:00
Robert Smallshire 7f50044b43 Makes SegYReader compute the maximum number of samples per trace before serializing (to the cache). 2015-06-08 23:38:24 +02:00
Robert Smallshire ebb62fbc7b Fixes SortedSet.__new__ failure to return object 2015-06-08 23:36:42 +02:00
Robert Smallshire ee9f2cbb52 Adds an extract_inline_3d() function to segpy_numpy 2015-06-08 21:37:03 +02:00
Robert Smallshire 6d986a21a9 Adds code for extraction of an inline. 2015-06-07 22:35:32 +02:00
Robert Smallshire 23ee609c50 Improves handling of SEG Y data loaded from file-like objects without filenames (e.g. BytesIO) in respect of Reader caching 2015-06-07 22:12:36 +02:00
Robert Smallshire f89de23b82 Adds persistent cacheing of SegYReader objects.
SegYReader objects are now cached by default in a persistent
store of pickles. Cached readers are located on the basis of
content hashes of the SEG Y files to which they relate combined
with the other arguments used to create the orginal reader. This
can lead to significant speed-ups since all indexes owned by the
reader instance are also cached.
2015-06-07 22:00:25 +02:00
Robert Smallshire d06c2918d3 Add a function for computing the SHA1 hash of file contents. 2015-06-07 21:50:38 +02:00
Robert Smallshire 28046a19e2 Make HeaderPacker pickleable.
HeaderPacker requires special pickling support because the
standard library Struct object is not pickleable. Instead we
pickle the format string extracted from the Struct object when
pickling and use it to recreate the Struct object when
unpickling.
2015-06-07 20:12:05 +02:00
Robert Smallshire 3ff9308d02 Make Header and subclasses pickleable. 2015-06-07 20:09:16 +02:00
Robert Smallshire 986bae3da9 Fix mistake in LinearRegularCatalog.__repr__() 2015-06-07 20:02:23 +02:00
Robert Smallshire 60c66a249e Fix mistake in DictionaryCatalog2D.__repr__() 2015-06-07 19:45:13 +02:00
Robert Smallshire df312035e9 Merge branch 'ext-rework' 2015-06-04 22:27:49 +02:00
Robert Smallshire 3b740888bd Prevent the step argument to test_regular_constant_mapping being zero 2015-06-04 17:16:20 +02:00
Robert Smallshire 39988a2ca0 Removes the segpy_numpy/numpy directory. 2015-06-04 17:11:43 +02:00
Robert Smallshire 506ce10581 Removes segpy_numpy.__init__.py 2015-06-04 15:22:21 +02:00
Robert Smallshire 2049e89819 Remove the ext subpackage.
import segpy.ext.numpy will no longer be supported. It just doesn't play nice with a lot of Python tooling.  In future it will be
import segpy_numpy
2015-06-04 15:21:22 +02:00
Robert Smallshire 80966a18fb Add a small program for checking whether SEG Y files roundtrip through the data structure flawlessly. 2015-06-04 15:18:20 +02:00
Robert Smallshire db997625e3 Merge pull request #20 from rth/master
Replaced a call to HeaderPacker by make_header_packer
2015-06-04 11:56:38 +02:00
Robert Smallshire af1f6de5d9 Fixes a missed Markdown to reStructuredText conversion in CONTRIBUTING.rst 2015-06-04 10:40:38 +02:00
Robert Smallshire b8cd3f696d Fix links to the CLA in CONTRIBUTING.rst 2015-06-04 10:39:00 +02:00
Robert Smallshire 61de7cd172 Change the author in setup.py to Sixty North AS 2015-06-04 10:23:27 +02:00
Robert Smallshire e657342126 Update the contributing link in README.rst 2015-06-04 10:22:17 +02:00
Robert Smallshire eaf79b0e68 Converts CONTRIBUTING.rst to actual reStructuredText rather than Markdown. 2015-06-04 10:21:57 +02:00
Robert Smallshire 6158cf3279 Add contributors license agreements and instructions on how to contribute. 2015-06-04 10:08:20 +02:00
Roman Yurchak 40bfb76f29 Replaced a call to HeaderPacker by make_header_packer 2015-06-03 23:06:09 +02:00
Robert Smallshire d257ced760 Updates the license argument to setup() 2015-05-17 13:18:59 +02:00
Robert Smallshire 9c880b6715 Updates author contact information 2015-05-17 13:18:23 +02:00
Robert Smallshire 5e33d2731c Updates trove classifier for license. 2015-05-17 13:13:17 +02:00
Robert Smallshire 9f090c2381 Adds Python 3 shebangs to command-line scripts. 2015-05-14 10:04:19 +02:00
Robert Smallshire ce08d5b99a Adds tests for textual reel header with arbitrary content. Removes seek() calls from write_textual_reel_header() and write_binary_reel_header(). 2015-05-12 10:59:09 +02:00
Robert Smallshire d0a1eb5a47 Test round-tripping of binary reel headers to and from a byte stream. 2015-05-12 10:01:42 +02:00
Robert Smallshire 5bfba47daf A Hypothesis strategy for producing headers of a particular class. 2015-05-11 22:19:57 +02:00
Robert Smallshire a4e4e78ee8 SegyReader3D now uses updates 2D catalogs for traces. 2015-05-08 19:18:48 +02:00
Robert Smallshire c1b2dfed86 More flexible and robust 2D catalog types which independently store i and j indexes. 2015-05-08 18:41:04 +02:00
Robert Smallshire 917e0a1ebe Additional Hypothesis-based tests for the catalog builder and catalog implementations. 2015-05-07 22:24:23 +02:00
Robert Smallshire 9ebb5c7477 Improved formatting (indentation). 2015-05-07 22:23:50 +02:00
Robert Smallshire d57dcef777 Fixes a missing condition in CatalogBuilder which caused a failure on mappings with arbitrary keys and values with a regular stride. (Found by Hypothesis, of course). 2015-05-07 21:58:33 +02:00
Robert Smallshire c713475e07 Removes Catalog base class and fixes defects in CatalogBuilder and RegularCatalog detected by Hypothesis. 2015-05-07 21:15:19 +02:00
Robert Smallshire 4c222b96a6 Removes unnecessary code from CatalogBuilder. 2015-05-07 20:34:56 +02:00
Robert Smallshire f491fe80b1 And here's the test with did the good work leading to the previous commit. 2015-05-07 20:34:13 +02:00
Robert Smallshire 8c484a1ffa Fix defects in the CatalogBuilder and LinearRegularCatalog detected by a Hypothesis test. 2015-05-07 20:33:29 +02:00
Robert Smallshire 2a22e8588f Improves documentation and formatting. 2015-05-07 15:57:42 +02:00
Robert Smallshire 1ba97f75ce Improves and corrects documentation. 2015-05-07 15:57:11 +02:00
Robert Smallshire b042133f55 Adds to and fixes documentation. Removes Python 2 cruft. 2015-05-07 15:56:31 +02:00
Robert Smallshire 71ad80b016 Merge branch 'master' of https://github.com/sixty-north/segpy 2015-05-07 14:35:25 +02:00
Robert Smallshire 0c0096f314 Adds basic support for decimal version representation. 2015-05-07 14:34:57 +02:00
Robert Smallshire b4e98a562f Corrects the official values for SEGY_REVISION_0 and SEGY_REVISION_1 constants. Tidies up the version canonicalization code. 2015-05-07 14:04:48 +02:00
Robert Smallshire fa5371349d The SEG Y Revision 1 specification. 2015-05-07 13:54:50 +02:00
Robert Smallshire 32eeb6e2f3 PEP 8 compliance. 2015-05-07 13:53:13 +02:00
Robert Smallshire c839a49531 Removes unnecessary print statement. 2015-05-07 13:52:46 +02:00
Robert Smallshire 68c5289a91 Renames arguments for correctness and clarity, to avoid name clashes. 2015-05-07 13:52:10 +02:00
Robert Smallshire 9dbc9d15dc Improves API documentation for reader.py. 2015-05-07 13:51:01 +02:00
Robert Smallshire 89a8b2420f Improves readability and formatting in datatypes.py. 2015-05-07 13:50:12 +02:00
Robert Smallshire b52ea4cff6 Simplifies comparison statements in encoding.py 2015-05-07 13:49:38 +02:00
Robert Smallshire 2e9064432c Update DESCRIPTION.rst 2015-05-07 12:59:54 +02:00
Robert Smallshire edd1fce166 Adds clarifying remark to pairwise() generator function. 2015-05-06 20:37:46 +02:00
Robert Smallshire 3269e13705 Removes unnecessary __init__.py file. 2015-05-06 20:21:10 +02:00
Robert Smallshire 449fecd757 Updates the timeslice.py example to use the new inline_numbers() and xline_numbers() methods. 2015-05-06 18:19:38 +02:00
Robert Smallshire a50c947a95 Removes cdp_range(), inline_range() and xline_range() methods which could have misleading results. Introduces cdp_numbers(), inline_numbers() and xline_numbers() which are always accurate. 2015-05-06 16:27:45 +02:00
Robert Smallshire 90320f6a42 Removes debugging code. 2015-05-06 12:53:23 +02:00
Robert Smallshire b0e02e2f8e Removes dead code from IBMFloat. 2015-05-06 12:51:39 +02:00
Robert Smallshire 05ecd3423e Removing interning of IBMFloat instances gives a 20% performance improvement reading trace data. 2015-05-06 12:50:45 +02:00
Robert Smallshire fd35fd4cd6 Removes the Python 2 trove classifier 2015-05-06 12:19:01 +02:00
Robert Smallshire fab758f9fc Removes hardwired binary reel header class type in write_binary_reel_header(). 2015-05-06 11:32:32 +02:00
Robert Smallshire 3f82865c2d Refactors conditional to polymorphism in HeaderPacker. About 5% faster at reading trace headers. 2015-05-06 11:31:12 +02:00
Robert Smallshire 002385fc84 Refactors HeaderPacker.unpack() 2015-05-06 11:04:26 +02:00
Robert Smallshire 0801fc2c17 Documents Header.__init__() 2015-05-06 10:47:43 +02:00
Robert Smallshire b5af594716 Achieves another 30% increase in trace header reading performance. (Forgotten file) 2015-05-06 10:39:31 +02:00
Robert Smallshire abf093bce2 Achieves another 30% increase in trace header reading performance. 2015-05-06 10:38:32 +02:00
Robert Smallshire 05b1352730 Corrects incorrect offset for year_recorded field. 2015-05-06 10:26:32 +02:00
Robert Smallshire b0243c984e Reimplements Header.__init__() to be almost twice as fast. 2015-05-06 09:54:14 +02:00
Robert Smallshire caa90a18ed Removes printing of all inline, crossline and shotpoint numbers from report.py example. 2015-05-06 09:27:46 +02:00
Robert Smallshire cdfb0c9088 Bumps version to 2.0.0a2 2015-05-05 20:23:02 +02:00
Robert Smallshire d44a4d60ae Rename textual_reel_header_definition to textual_reel_header 2015-05-05 20:16:06 +02:00
Robert Smallshire 44d6f4e3d9 Removes erroneous reference to 'rewrite branch' 2015-05-05 20:09:18 +02:00
Robert Smallshire d8e563deb0 Updates the Travis build status badge in README 2015-05-05 20:08:41 +02:00
Robert Smallshire 3449a81b80 Updated README to show master branch build status. 2015-05-05 19:54:23 +02:00
Robert Smallshire 71e77b0ef3 Updated contributor information. 2015-05-05 19:40:19 +02:00
Robert Smallshire 1bcdf29f0c Remove unused file. 2015-05-05 17:38:08 +02:00
Robert Smallshire aab5ebb81b Update readme. 2015-05-05 17:37:40 +02:00
Robert Smallshire a80a4282ac Update copyright message with current year. 2015-05-05 17:37:26 +02:00
Robert Smallshire 9e1921e4b6 Definition of the binary reel header and using the new definition in read and writing files. 2015-05-05 17:21:00 +02:00
Robert Smallshire b8058578d6 Update readme and license. 2015-05-05 13:50:22 +02:00
Robert Smallshire e25c9682d8 Relocate EMPTY_BYTE_STRING constant from portability to util. 2015-05-05 13:38:35 +02:00
Robert Smallshire 5c6238c528 Remove Python 2.7 crutch for unicode/str discrepancy. 2015-05-05 13:35:52 +02:00
Robert Smallshire 442444b0f1 Tidy up imports. 2015-05-05 13:34:07 +02:00
Robert Smallshire b90ff07940 Relocate four_bytes() from portability to util. 2015-05-05 13:33:48 +02:00
Robert Smallshire 6e3494586a Remove Python 2.7 crutch for izip and izip_longest 2015-05-05 13:30:32 +02:00
Robert Smallshire 60acf5f3db Remove Python 2.7 crutch for reprlib 2015-05-05 13:23:03 +02:00
Robert Smallshire c60ee01796 Remove Python 2.7 crutch for bytes constructor. 2015-05-05 13:21:25 +02:00
Robert Smallshire 0ec2c192a3 Remove Python 2.7 crutch for int/long 2015-05-05 13:17:45 +02:00
Robert Smallshire 2b7351848b Remove Python 2.7 crutch for file.seekable() 2015-05-05 13:17:07 +02:00
Robert Smallshire f0bd88ffe8 Remove Python 2.7 travis build. Python 3 only from now on. 2015-05-05 13:14:07 +02:00
Robert Smallshire bdfb0aaf86 Adds .hypothesis to .gitignore 2015-05-05 13:03:22 +02:00
Robert Smallshire e8cdaaf198 Use the HeaderPacker from toolkit to read and write the trace header. 2015-05-05 13:02:07 +02:00
Robert Smallshire 1311d2f84f Use the HeaderPacker from toolkit to read and write the trace header. 2015-05-03 19:52:44 +02:00
Robert Smallshire cf3b6f1f70 Distinguish between Rev 0 and Rev 1 trace header formats. 2015-04-27 12:51:39 +02:00
Robert Smallshire 7da4f755da All fields of the new trace-header definition. 2015-04-26 23:04:20 +01:00
Robert Smallshire 5cfc1805e9 Major work-in-progress on the binary header specification system. 2015-04-26 19:10:22 +01:00
Robert Smallshire 25d2387d39 Much improved design of header definition with automagical generation of a header DTO from the format. 2015-04-18 07:29:13 +01:00
Robert Smallshire 283517219d Merge branch 'master' into header 2015-04-17 12:20:13 +02:00
Robert Smallshire d7e531dee8 FIx Hypothesis 1.2 compatibilitity descriptors->specifiers. 2015-04-17 12:14:34 +02:00
Robert Smallshire 39e31afa28 Formatting. 2015-04-17 12:12:12 +02:00
Robert Smallshire 5ab4c70969 Merge branch 'ibm_float' 2015-04-17 12:11:08 +02:00
Robert Smallshire 90574122d5 Fixes for multiline text generation strategy in light of Hypothesis 1.2. 2015-04-17 11:43:32 +02:00
Robert Smallshire e57cb19f83 Require Hypothesis >= 1.2 2015-04-15 10:30:10 +02:00
Robert Smallshire deb9d6c8ca Read IBM floats into the new IBMFloat type rather than converting directly to IEEE floats. This allows bit-perfect round-tripping. 2015-04-15 09:33:14 +02:00
Robert Smallshire 88987a8755 Tightened up the tests now that Hypothesis performance has improved. 2015-04-15 09:32:20 +02:00
Robert Smallshire dd24ea6bd8 Improved and more extensive hypothesis tests for IBMFloat. 2015-04-14 21:50:56 +02:00
Robert Smallshire ff40995995 Implemented equality testing between IBMFloats 2015-04-14 21:50:30 +02:00
Robert Smallshire 8a5c2997b9 Comment out incomplete code. 2015-03-13 12:39:56 +01:00
Robert Smallshire b4b0fe5d92 Some progress on testing and implementing the IBMFloat. 2015-03-13 12:36:11 +01:00
Robert Smallshire 52e2967ec2 Some progress on testing and implementing the IBMFloat. 2015-03-10 19:52:06 +01:00
Robert Smallshire 1779f11b44 Adds a new IBMFloat type which losslessly stores IBM 32 bit floats 2015-03-06 18:58:38 +01:00
Robert Smallshire b026077e09 Merge branch 'rewrite' into header 2015-02-27 16:23:35 +01:00
Robert Smallshire b6fda775c2 Progress on the infrastructure for header definitions and instances. 2015-02-27 16:19:40 +01:00
Robert Smallshire 9751fa6300 Docstring for segpy.ext.load_packages() 2015-02-27 10:55:24 +01:00
Robert Smallshire e6ed86dc8d Relocate segpy.ext.numpyext to a separately deployable package segpy-numpy which using pkg_resources entry points to deploy itself into segpy.ext at runtime. 2015-02-26 20:25:42 +01:00
Robert Smallshire e938be8507 Fix trove classifier 2015-02-26 20:23:31 +01:00
Robert Smallshire 46ddafb37c Layout 2015-02-26 20:23:01 +01:00
Robert Smallshire 17a6475e5b Lower case Segpy->segpy in setup.py 2015-02-25 10:57:29 +01:00
Robert Smallshire b1782b9c81 Packaging work and explicit __version__ 2015-02-24 21:13:16 +01:00
Robert Smallshire 7d2079de83 Time slice extraction example program. 2015-02-24 17:28:41 +01:00
Robert Smallshire aac598d64f Update numpy dtypes extension for correct type codes. 2015-02-24 17:27:37 +01:00
Robert Smallshire 19c89d9029 Exploratory ideas for header definitions 2015-02-24 11:36:17 +01:00
Robert Smallshire 52cb3a7005 Documentation outline 2015-02-24 11:32:46 +01:00
Robert Smallshire 3fd47ac7e5 Rename complementary_slices() to complementary_intervals(). 2015-02-24 11:31:16 +01:00
Robert Smallshire 60e4295b29 Remove support for Python 3.2 2015-02-01 09:46:02 +01:00
Robert Smallshire 7c7b771efb Remove irrelevant history from the rewrite branch. 2015-02-01 09:12:02 +01:00
Robert Smallshire 8f4b79e906 Fixes Unicode error on Python 2.7. 2015-01-31 22:37:54 +01:00
Robert Smallshire 35678d9ec9 Test end-text stanza present in extended headers. 2015-01-31 21:55:22 +01:00
Robert Smallshire 469832416e Use integers_in_range to set the batch size rather than simply int to prevent colossal memory allocations. 2015-01-31 21:29:42 +01:00
Robert Smallshire 1e4494298e Quickstarted Sphinx documentation 2015-01-31 17:59:43 +01:00
Robert Smallshire b91dd65d82 Removed dependency on unused sys module. 2015-01-31 17:51:22 +01:00
Robert Smallshire 1d20aaf820 Adds Python 3.2 to Travis CI build 2015-01-31 16:19:38 +01:00
Robert Smallshire 78bae6f859 Adds Python 3.3 to Travis CI build 2015-01-31 16:16:16 +01:00
Robert Smallshire 6e8843dce6 Adds Python 2.7 to Travis CI build 2015-01-31 16:10:21 +01:00
Robert Smallshire 99b37024ab Tidy up imports. 2015-01-31 16:08:48 +01:00
Robert Smallshire 77d5ffb10f Fixed broken import. 2015-01-31 16:06:22 +01:00
Robert Smallshire 4862f347df Reinstate Python 2 compatibility. 2015-01-31 16:00:14 +01:00
Robert Smallshire 38511e67ba Update the README to include a status image. 2015-01-31 09:10:13 +01:00
Robert Smallshire c9e9e7c599 Point Travis CI and the right test-requirements.txt file 2015-01-31 09:00:00 +01:00
Robert Smallshire 842e16f591 Add a .travis.yml file for Travis CI setup 2015-01-31 08:54:19 +01:00
Robert Smallshire 955eedcf94 Add test-requirements.txt to specify external dependencies for running tests. 2015-01-31 08:53:45 +01:00
Robert Smallshire 0d61bc782e Use the EMPTY_BYTE_STRING constant instead of b'' 2015-01-30 21:00:21 +01:00
Robert Smallshire d454f2b4eb Use the array module rather than the struct module for reading the sample arrays. Gives a x3 speed-up. Also fix automatic encoding detection. 2015-01-30 20:57:26 +01:00
Robert Smallshire 5322c2a5d2 Example to time header-reading/catalog-building and trace reading. 2015-01-30 20:54:20 +01:00
Robert Smallshire a152672bf5 Example program to print a report of important SEG Y file characteristics. 2015-01-30 20:53:07 +01:00
Robert Smallshire 797cfee020 Sweep frequency start definition was missing from the reel header. 2015-01-30 20:52:14 +01:00
Robert Smallshire 5a1f790ffd Added a simple timed reader example. Also useful for performance testing changes 2015-01-30 16:04:42 +01:00
Robert Smallshire 2d13fc8c82 Removed unused item_size argument from unpack_values() 2015-01-30 14:55:50 +01:00
Robert Smallshire f1310459d7 write_binary_reel_header() was not forwarding it's endian argument to write_binary_values() 2015-01-30 14:52:46 +01:00
Robert Smallshire c04cf0901f General code clean-up of issue PyCharm linter is highlighting. 2015-01-30 14:44:08 +01:00
Robert Smallshire e56eefbd2c Return double-precision IEEE float values when reading single-precision IBM floats to prevent loss of precision. 2015-01-30 14:36:35 +01:00
Robert Smallshire f8c298a971 Correctness and robustness improvements to the IBM <-> IEEE floating point conversions, including additional tests. 2015-01-30 14:34:27 +01:00
Robert Smallshire e74f4498aa Fixes issues revealed by the new loadsave.py example program. 2015-01-29 13:26:25 +01:00
Robert Smallshire ee1e05a9ff Forward endian parameter in read_binary_values() 2015-01-29 09:45:03 +01:00
Robert Smallshire 81728459e7 Removed dead code. Tightened up use of encodings. 2015-01-29 09:44:25 +01:00
Robert Smallshire 7bad88ce57 Simplified extended textual header formatting. 2015-01-29 09:43:43 +01:00
Robert Smallshire 2e342e5277 Merge branch 'rewrite' of github.com:rob-smallshire/segpy into rewrite 2015-01-28 21:50:15 +01:00
Robert Smallshire ccbedfa150 High level SEG Y writer. Work in progress 2015-01-28 21:50:04 +01:00
Robert Smallshire 255a490914 Code reorganisation - segpy is now a proper Python package. Rework of the extended textual header for symmetry with the reader. Some additional tests. 2015-01-28 21:42:52 +01:00
Robert Smallshire 8c1fe0742d Merge pull request #14 from wassname/patch-3
Fixed pack traceheader error
2015-01-28 14:49:21 +01:00
Robert Smallshire 2709785169 Merge pull request #13 from wassname/patch-2
Added missing argument for use of SegYReader
2015-01-28 14:48:11 +01:00
wassname 9444567b38 Fixed pack traceheader error
In python 3.3 I got the error: error: pack expected 91 items for packing (got 1), which is fixed is I pass the traceheader's items in as separate arguments by adding an *.
2015-01-28 16:30:55 +08:00
wassname 0f5db22d41 Added missing argument for use of SegYReader 2015-01-28 15:54:20 +08:00
Robert Smallshire 246b8515e4 Pass the trace header format to the read_trace_header() function. 2014-12-03 21:52:14 +01:00
Robert Smallshire 8f35206733 Merge key trace header correctness fixes from the rewrite branch into master 2014-12-03 20:52:22 +01:00
Robert Smallshire 1fd7a5d4c6 SegYReader3D.trace_index now accepts an (inline, crossline) tuple rather than two separate arguments. 2014-12-03 20:41:12 +01:00
Robert Smallshire 91e325c95a Some progress towards complete support for writing SEG Y 2014-12-03 20:38:08 +01:00
Robert Smallshire 2ee69544de Toolkit functions for writing headers. 2014-10-23 14:16:17 +02:00
Robert Smallshire 6f3aba6a4d A function for padding iterables. 2014-10-23 14:10:58 +02:00
Robert Smallshire 905ec4fe39 Support for regular and extended textual headers. 2014-10-23 11:41:22 +02:00
Robert Smallshire ceb0f9bd61 A function to guess whether a byte stream contains EBCDIC or ASCII text. 2014-10-23 11:40:00 +02:00
Robert Smallshire d2f1beac9d Support for reading textual (normal and extended) headers. Support for alternative inline/xline numbering in 3D seismic. 2014-10-23 11:39:16 +02:00
Robert Smallshire d50dc0b097 Adds a function for batching iterable series. 2014-10-23 11:37:07 +02:00
Robert Smallshire ef58b37e6c Fix fencepost errors for row-major indexing. 2014-10-23 11:33:07 +02:00
Robert Smallshire 27d78b6b6a Remove commented out line. 2014-10-23 11:32:07 +02:00
Robert Smallshire 7ac0083a52 Extracted Python 2/3 specific code into the portability module 2014-10-21 15:14:51 +02:00
Robert Smallshire ee228e2dc9 Improved portability between Python 2 and Python 3. Beginnings of code for writing SEG Y. 2014-10-21 14:54:49 +02:00
Robert Smallshire 1abff28a40 Adds optional progress reporting to long running functions 2014-09-03 11:29:28 +02:00
Robert Smallshire ddf4f2f6ab Construct a trace-length catalog giving the number of samples per trace 2014-09-03 09:50:06 +02:00
Robert Smallshire dfb92978f3 Adds additional catalog types for catalogs with a single constant value shared amongst all keys 2014-09-03 09:42:24 +02:00
Robert Smallshire fd0de79d18 Typo in TRACE_HEADER_DEF 2014-09-02 13:42:05 +02:00
Robert Smallshire 8eaea4b066 Merge branch 'abingham-rewrite' into rewrite 2014-09-02 13:36:41 +02:00
Robert Smallshire b0fe25b809 Manual merge of abingham pull request 2014-09-02 13:36:14 +02:00
Robert Smallshire 5f1bf53992 Fixes issue #7 - incorrect type in TRACE_HEADER_DEF 2014-09-02 13:26:45 +02:00
Robert Smallshire 9ddae466dd Typos 2014-09-02 11:11:56 +02:00
Robert Smallshire bcadf4478a Formatting 2014-09-02 11:10:07 +02:00
Robert Smallshire 8b9931cd84 Typos and using wrong variable. 2014-09-02 11:06:47 +02:00
Robert Smallshire 81f55a3058 Formatting 2014-09-02 11:06:16 +02:00
Robert Smallshire 8f6e0daf30 Renamed inlines_xlines() method to inline_xline_numbers() since that is what it actually yields. 2014-09-02 11:03:14 +02:00
Robert Smallshire b4800551fd Fixed scrambled code 2014-09-02 11:02:17 +02:00
Austin Bingham e02b7be182 Mostly PEP8 cleanups in the course of reading the code.
I took the liberty of adding a few specific exception types, some
documentation, and things like that, but this is mostly a cosmetic chance.
2014-09-02 09:39:04 +02:00
Robert Smallshire dcac37022d General cleanup. 2014-09-01 13:58:34 +02:00
Robert Smallshire 3b8f3ac26c Compile a struct format for trace headers 2014-08-27 15:57:27 +02:00
Robert Smallshire c6ebe9f3d6 Fixes blunders in trace header definition 2014-08-27 15:56:10 +02:00
Robert Smallshire 3a885b93ca Ignore data directory. 2014-08-27 15:55:45 +02:00
Robert Smallshire 8e08508ac6 Simplify code. 2014-08-27 10:24:54 +02:00
Robert Smallshire e64ec24bf7 Minor formatting consistency. 2014-08-27 10:06:44 +02:00
Robert Smallshire b816f7787b More PEP 8 naming compliance 2014-08-27 09:47:29 +02:00
Robert Smallshire 283070d491 Improved handling and tolerance of non-compliant SEG Y revisions. 2014-08-27 09:42:29 +02:00
Robert Smallshire b52aae71e4 More renaming towards PEP 8 standards. 2014-08-27 09:21:10 +02:00
Robert Smallshire 24eaa56908 Rename header and trace-header definition constant dictionaries to be more documentary, and in upper case. 2014-08-27 09:03:28 +02:00
Robert Smallshire 2e00902ed0 Don't unnecessarily pass the filename around on the read-path. 2014-08-27 08:55:53 +02:00
Robert Smallshire eb47898ae3 Removes unused legacy file. 2014-08-27 08:47:18 +02:00
Robert Smallshire 7e40a26196 Improves handling of mutable default arguments (empty dictionaries). 2014-08-27 08:46:37 +02:00
Robert Smallshire 15c9bf81c4 Merge pull request #6 from abingham/master
PEP8 cleanup
2014-08-26 18:28:56 +02:00
Austin Bingham 4cb5cf539a Replaced camelCase with undercaps. 2014-08-05 19:55:06 +02:00
Austin Bingham 7014fba323 Added command-line arguments to test. 2014-08-05 19:46:15 +02:00
Austin Bingham 662f7c67bb Updated test to use proper readSegy API.
Also structured it a little more sanely, and PEP8'd it.

Conflicts:
	testsegy.py
2014-08-05 19:27:52 +02:00
Austin Bingham 1c19444946 PEP8 cleanup 2014-08-05 16:14:44 +02:00
Robert Smallshire 24da1dd6cb Typo in README.rst 2014-07-19 22:17:14 +02:00
Robert Smallshire 1767538b5e Merge pull request #1 from VelizarVESSELINOV/master
Missing import, IBM float data management
2014-07-19 22:06:06 +02:00
VelizarVESSELINOV cabe8d71e1 Revision mismatch during BPS computation 2014-07-19 10:04:35 -07:00
VelizarVESSELINOV 99f65c9c8e Missing import, IBM float data management 2014-07-19 09:43:16 -07:00
Robert Smallshire 701c75f926 Update README.rst 2014-07-19 18:09:57 +02:00
Robert Smallshire c7eb00264e Merge branch 'master' of github.com:rob-smallshire/segpy 2014-07-19 17:51:19 +02:00
Robert Smallshire 34df01b65d Delete .hgtags 2014-07-19 17:51:14 +02:00
Robert Smallshire 4b1b4ce3e0 Added a .gitignore file 2014-07-19 17:51:00 +02:00
Robert Smallshire 89cb5a5d28 Emboldened enjoiner 2014-07-19 17:36:26 +02:00
Robert Smallshire 3eb58caae4 Update and rename README to README.rst 2014-07-19 17:35:28 +02:00
Robert Smallshire 429d0b8b24 Update README 2014-07-19 17:32:05 +02:00
Robert Smallshire 3d236ed96b Removing IDEA files 2012-03-25 19:38:57 +02:00
Robert Smallshire 31a88eb4b3 Refactoring for readability and performance. Reading trace headers is now about twice as fast. 2011-10-23 14:01:16 +02:00
Robert Smallshire 48d0b7b684 Minor improvements 2011-10-21 22:05:56 +02:00
Robert Smallshire 6b0399a5a2 PyCharm settings. 2011-10-20 22:15:30 +02:00
Robert Smallshire 00f1347658 Move IBM/IEEE float code to a separate module 2011-10-20 22:14:51 +02:00
Robert Smallshire f9639100a6 Close file properly. 2011-10-20 22:02:43 +02:00
Robert Smallshire 9adcc9bd79 Relocated header definitions into separate files. 2011-10-20 22:01:11 +02:00
Robert Smallshire 411453b765 Removed ununused classes and relocated pylab dependend plotting code to another module. 2011-10-20 21:38:14 +02:00
Robert Smallshire 5a359c8a1f Simplications for switching on type - using dictionaries and functions rather then if..elif.. 2011-10-20 21:33:28 +02:00
Robert Smallshire 9af1c27131 Simplify code to handle Seg Y revsions 0 and 1 and remove magic numbers 2011-10-20 19:47:39 +02:00
Robert Smallshire d4831516f3 More logging cleanup - inline unnecessary variables 2011-10-20 19:21:54 +02:00
Robert Smallshire df6edc96aa Move over to use standard library logging. 2011-10-20 19:10:36 +02:00
Robert Smallshire 35b987ea21 Readability: Spaces around operators 2011-10-19 23:17:20 +02:00
Robert Smallshire 6ccf6bac71 Replace magic numbers with constants. 2011-10-19 00:34:18 +02:00
Robert Smallshire 865994b1ad Readability: Even more spaces around operators. 2011-10-19 00:25:15 +02:00
Robert Smallshire d5f3feff2e Readability: More spaces around operators. 2011-10-18 21:42:29 +02:00
Robert Smallshire e1225e4ab6 Readability: Spaces around operators. 2011-10-18 21:27:47 +02:00
Robert Smallshire a62be5d2f5 Remove unnecessary comment noise 2011-10-18 19:16:32 +02:00
Robert Smallshire 0d5adb96cc Remove redundant semicolons 2011-10-18 19:13:46 +02:00
Robert Smallshire 687c0096ab Readability: Spaces after colons 2011-10-18 19:10:05 +02:00
Robert Smallshire fc4e21ee38 Readability: spaces after commas 2011-10-18 19:08:37 +02:00
Robert Smallshire 28e20cd055 Fixed obvious problem with IBM float conversion. 2011-10-18 19:02:49 +02:00
Robert Smallshire e66d3ec210 Removed dead code. 2011-10-18 19:01:58 +02:00
Robert Smallshire 4150208569 Removed duplicate dictionary key. 2011-10-18 18:56:07 +02:00
Robert Smallshire 92ae363eb5 Remove redundant parentheses 2011-10-18 18:54:33 +02:00
Robert Smallshire 98c1ba1385 More spellings. 2011-10-18 18:48:24 +02:00
Robert Smallshire af098c9950 Remove unused import. 2011-10-18 18:46:13 +02:00
Robert Smallshire 0615ce8fc9 Improve spelling. 2011-10-18 18:44:58 +02:00
Robert Smallshire a7842ab28b Remove support for legacy numarray and Numeric packages. Now imports successfully with numpy. 2011-10-18 18:34:03 +02:00
Robert Smallshire deaa39982f Convert all indentation to spaces and remove tabs 2011-10-18 18:32:17 +02:00
Robert Smallshire d8bcd286a4 Adding temporarily reworked segypy2.py 2011-10-17 21:55:09 +02:00
convert-repo 7fa753d695 update tags 2011-10-17 16:59:03 +00:00
cultpenguin 7b57e945a5 Many improvements by Andre Squelch 2007-03-01 09:10:55 +00:00
cultpenguin 2fceed9e1a *** empty log message *** 2006-06-20 11:26:40 +00:00
cultpenguin c555ded3c5 *** empty log message *** 2006-06-20 11:23:49 +00:00
cultpenguin c7631981dc *** empty log message *** 2005-10-27 10:38:21 +00:00
cultpenguin c6c5a64055 *** empty log message *** 2005-10-27 08:48:26 +00:00
cultpenguin aef29e7a1f *** empty log message *** 2005-10-26 09:11:43 +00:00
cultpenguin 53b94d1e7b *** empty log message *** 2005-10-17 11:24:52 +00:00
cultpenguin 6b67bb64ba gui update 2005-10-17 10:10:05 +00:00
cultpenguin 9bf2afa3da *** empty log message *** 2005-10-13 11:29:22 +00:00
cultpenguin 881acb4abe *** empty log message *** 2005-10-13 09:35:52 +00:00
cultpenguin 25ac9f7201 *** empty log message *** 2005-10-10 07:45:40 +00:00
cultpenguin 60dd2ed06e *** empty log message *** 2005-10-10 07:35:42 +00:00
cultpenguin 6556b5900c *** empty log message *** 2005-10-09 21:23:40 +00:00
cultpenguin 91ce42ea98 *** empty log message *** 2005-10-09 21:09:46 +00:00
cultpenguin bce3bcb69b *** empty log message *** 2005-10-08 19:48:49 +00:00
cultpenguin 95e73fed91 *** empty log message *** 2005-10-08 11:41:15 +00:00
cultpenguin cadabf955b *** empty log message *** 2005-10-01 14:48:13 +00:00
cultpenguin f8dc5eee79 *** empty log message *** 2005-09-30 22:32:00 +00:00
cultpenguin 221cee74e9 *** empty log message *** 2005-09-30 22:26:59 +00:00
cultpenguin cf3154a61e SEGY writing support.... 2005-09-26 20:29:37 +00:00
cultpenguin 0dc603820f *** empty log message *** 2005-07-11 12:12:37 +00:00
cultpenguin 25b2371ae1 *** empty log message *** 2005-07-10 23:20:12 +00:00
cultpenguin 1b53f5b0f1 IBM float OK 2005-07-10 23:17:38 +00:00
cultpenguin 62ddbfe136 *** empty log message *** 2005-05-23 19:58:43 +00:00
cultpenguin 970b093dfe *** empty log message *** 2005-05-22 12:45:43 +00:00
cultpenguin bbb2a9e090 1,2,4Byte Int and IEEE OK 2005-05-11 19:35:06 +00:00
cultpenguin 23446f6e81 Some bad shifting og seismogram for IBM floats 2005-05-10 16:42:43 +00:00
cultpenguin f3e0e86eb5 Some bad shifting og seismogram for IBM floats 2005-05-10 16:41:35 +00:00
cultpenguin 354442f780 IBM FLOATS OK, BUT RESHAPING IS BAD ipython -pylab testsegy.py 2005-05-10 14:46:48 +00:00
cultpenguin 0c8cb9a95b *** empty log message *** 2005-05-10 11:02:40 +00:00
cultpenguin 3b7a1ec421 *** empty log message *** 2005-05-09 20:15:39 +00:00
cultpenguin a645b0ea28 *** empty log message *** 2005-04-21 09:01:54 +00:00
cultpenguin 125ffa670f SegyTraceHeader rev0/1 deifinitions done 2005-04-21 07:38:45 +00:00
cultpenguin b617bb8975 *** empty log message *** 2005-04-20 20:23:19 +00:00
cultpenguin fa56e5b641 *** empty log message *** 2005-04-20 20:22:05 +00:00
cultpenguin bc8ec143ed Starting to implement Header information dictionaries 2005-04-20 08:33:53 +00:00
cultpenguin 75e154c001 *** empty log message *** 2005-04-18 15:50:24 +00:00
cultpenguin 0e49003e36 *** empty log message *** 2005-04-18 15:34:08 +00:00
cultpenguin 3636e8661c Much Faster Approach 2005-04-14 15:16:16 +00:00
cultpenguin 511b531837 *** empty log message *** 2005-04-14 14:26:32 +00:00
60 changed files with 11864 additions and 249 deletions
+64
View File
@@ -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
+9
View File
@@ -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
+102
View File
@@ -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/>`_
+32
View File
@@ -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.
+665
View File
@@ -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
View File
@@ -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
View File
@@ -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."
+2
View File
@@ -0,0 +1,2 @@
sphinx
sphinx_rtd_theme
+242
View File
@@ -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
+272
View File
@@ -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
+49
View File
@@ -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.
+46
View File
@@ -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
+55
View File
@@ -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())
+78
View File
@@ -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())
+70
View File
@@ -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())
+140
View File
@@ -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())
+20
View File
@@ -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())
+108
View File
@@ -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))
+5
View File
@@ -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
+127
View File
@@ -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': [
]
},
)
+1
View File
@@ -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()
+1
View File
@@ -0,0 +1 @@
__version__ = '2.0.0a2'
+223
View File
@@ -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."
)
+585
View File
@@ -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)
+51
View File
@@ -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]
+71
View File
@@ -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()
+84
View File
@@ -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
View File
@@ -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
+441
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+65
View File
@@ -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)
+75
View File
@@ -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)
+147
View File
@@ -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())
+927
View File
@@ -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)
+561
View File
@@ -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)"
)
+29
View File
@@ -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
View File
@@ -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
View File
@@ -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()
-207
View File
@@ -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)
+5
View File
@@ -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
+118
View File
@@ -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': [
],
},
)
+1
View File
@@ -0,0 +1 @@
+70
View File
@@ -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))
+1
View File
@@ -0,0 +1 @@
hypothesis>=1.11
+91
View File
@@ -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))
+43
View File
@@ -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()
+361
View File
@@ -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()
+73
View File
@@ -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))
+378
View File
@@ -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()
+80
View File
@@ -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
View File
@@ -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);