diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst new file mode 100644 index 0000000..898081a --- /dev/null +++ b/DESCRIPTION.rst @@ -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.2 and higher (and 2.7 for now). 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. diff --git a/README.rst b/README.rst index 908cb45..14c276d 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -===== -Segpy -===== +======= +Segpy 2 +======= Status ====== @@ -18,4 +18,11 @@ geophysical seismic data. It is an open standard, and is controlled by the SEG T non-profit organization. This project aims to implement an open SEG Y module in Python for transporting seismic data between SEG Y files and -Numpy arrays. Segpy is a package for reading, writing and manipulating SEG Y data in pure Python. +Python data structures in pure Python. + +Segpy Versions +============== + +Segpy 2.0 is a complete re-imagining of a SEG Y reader in Python and represents a complete break from Segpy 1.0 in terms +of the interface it presents to clients and the implementation behind those interfaces. Segpy 1.0 should be considered +unmaintained legacy software. The present and future of Segpy is Segpy 2. \ No newline at end of file diff --git a/examples/timeslice.py b/examples/timeslice.py new file mode 100644 index 0000000..7755c4f --- /dev/null +++ b/examples/timeslice.py @@ -0,0 +1,137 @@ +"""Extract a timeslice from a 3D seismic volume to a Numpy 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 +""" +from __future__ import print_function + +import argparse +import os +import sys +import traceback + +import numpy as np + +from segpy.reader import create_reader +from segpy_numpy.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_line_range = segy_reader.inline_range() + x_line_range = segy_reader.xline_range() + + i_size = len(i_line_range) + x_size = len(x_line_range) + 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((x_size, i_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 = inline_num - i_line_range.start + x_index = xline_num - x_line_range.start + timeslice[x_index, i_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()) + diff --git a/segpy-ext/segpy-numpy/DESCRIPTION.rst b/segpy-ext/segpy-numpy/DESCRIPTION.rst new file mode 100644 index 0000000..4b735c4 --- /dev/null +++ b/segpy-ext/segpy-numpy/DESCRIPTION.rst @@ -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. diff --git a/segpy-ext/segpy-numpy/__init__.py b/segpy-ext/segpy-numpy/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/segpy-ext/segpy-numpy/__init__.py @@ -0,0 +1 @@ + diff --git a/segpy-ext/segpy-numpy/segpy_numpy/__init__.py b/segpy-ext/segpy-numpy/segpy_numpy/__init__.py new file mode 100644 index 0000000..420f4ca --- /dev/null +++ b/segpy-ext/segpy-numpy/segpy_numpy/__init__.py @@ -0,0 +1,10 @@ +print("segpy_numpy.__init__ imported") + +from pkgutil import extend_path + +#__path__ = extend_path(__path__, __name__) +__version__ = '2.0.0a1' + + +def load(): + pass diff --git a/segpy-ext/segpy-numpy/segpy_numpy/numpy/__init__.py b/segpy-ext/segpy-numpy/segpy_numpy/numpy/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/segpy-ext/segpy-numpy/segpy_numpy/numpy/__init__.py @@ -0,0 +1 @@ + diff --git a/segpy/ext/numpyext.py b/segpy-ext/segpy-numpy/segpy_numpy/numpy/dtypes.py similarity index 73% rename from segpy/ext/numpyext.py rename to segpy-ext/segpy-numpy/segpy_numpy/numpy/dtypes.py index 1fff0dc..2583caf 100644 --- a/segpy/ext/numpyext.py +++ b/segpy-ext/segpy-numpy/segpy_numpy/numpy/dtypes.py @@ -2,11 +2,11 @@ import numpy -NUMPY_DTYPES = {'ibm': numpy.dtype('f4'), - 'l': numpy.dtype('i4'), - 'h': numpy.dtype('i2'), - 'f': numpy.dtype('f4'), - 'b': numpy.dtype('i1')} +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): diff --git a/segpy-ext/segpy-numpy/setup.cfg b/segpy-ext/segpy-numpy/setup.cfg new file mode 100644 index 0000000..79bc678 --- /dev/null +++ b/segpy-ext/segpy-numpy/setup.cfg @@ -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 diff --git a/segpy-ext/segpy-numpy/setup.py b/segpy-ext/segpy-numpy/setup.py new file mode 100644 index 0000000..c604eaf --- /dev/null +++ b/segpy-ext/segpy-numpy/setup.py @@ -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/rob-smallshire/segpy', + + # Author details + author='Robert Smallshire', + author_email='robert@smallshire.org.uk', + + # Choose your license + license='GPL', + + # 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 :: 2.7', + 'Programming Language :: Python :: 3', + '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 '/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={ + 'segpy.ext': 'segpy-numpy = segpy_numpy' + }, +) + + diff --git a/segpy/__init__.py b/segpy/__init__.py index 8b13789..a790050 100644 --- a/segpy/__init__.py +++ b/segpy/__init__.py @@ -1 +1 @@ - +__version__ = '2.0.0a1' diff --git a/segpy/ext/__init__.py b/segpy/ext/__init__.py index 8b13789..ba891c3 100644 --- a/segpy/ext/__init__.py +++ b/segpy/ext/__init__.py @@ -1 +1,20 @@ +import pkg_resources +loaded = set() + + +def load_entry_points(name=None): + """Load extension packages into the segpy.ext namespace. + + Any packages registered against the 'segpy.ext' entry-point group will be + installed dynamically into the segpy.ext namespace. + """ + for entry_point in pkg_resources.iter_entry_points(group='segpy.ext', name=name): + package = entry_point.load() + if package not in loaded: + loaded.add(package) + __path__.extend(package.__path__) + package.load() + + +load_entry_points() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..79bc678 --- /dev/null +++ b/setup.cfg @@ -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 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..cb1e41d --- /dev/null +++ b/setup.py @@ -0,0 +1,119 @@ +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/rob-smallshire/segpy', + + # Author details + author='Robert Smallshire', + author_email='robert@smallshire.org.uk', + + # Choose your license + license='GPL', + + # 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 :: 2.7', + 'Programming Language :: Python :: 3', + '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 '/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': [ + ], + }, +) + diff --git a/test/test_util.py b/test/test_util.py index 40f0c3d..1b71b94 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -15,7 +15,7 @@ class TestBatched(unittest.TestCase): self.assertTrue(all(len(batch) == batch_size for batch in batches[:-1])) @given([int], - integers_in_range(1, 1000)) + integers_in_range(1, 1000)) def test_final_batch_sizes(self, items, batch_size): assume(len(items) > 0) assume(batch_size > 0)