[segyio] 39/376: Improved depth slice support with writing and slicing

Jørgen Kvalsvik jokva-guest at moszumanska.debian.org
Wed Sep 20 08:04:04 UTC 2017


This is an automated email from the git hooks/post-receive script.

jokva-guest pushed a commit to branch debian
in repository segyio.

commit 61889127eb98bc1a0ba58ac095c9df041a373dec
Author: jeanpaul <jpb at prador.net>
Date:   Sun Oct 16 12:26:38 2016 +0200

    Improved depth slice support with writing and slicing
---
 python/segyio/CMakeLists.txt  |  25 ++++----
 python/segyio/_depth_plane.py |  18 ------
 python/segyio/_line.py        |   7 ++-
 python/segyio/segy.py         | 119 ++++++++++++++++++++++++++++--------
 tests/test_segy.py            | 137 +++++++++++++++---------------------------
 tests/test_segyio_c.py        |  12 ++++
 6 files changed, 171 insertions(+), 147 deletions(-)

diff --git a/python/segyio/CMakeLists.txt b/python/segyio/CMakeLists.txt
index c2a0671..03cdafd 100644
--- a/python/segyio/CMakeLists.txt
+++ b/python/segyio/CMakeLists.txt
@@ -1,16 +1,15 @@
 set(PYTHON_SOURCES
-    __init__.py
-    _depth_plane.py
-    _header.py
-    _line.py
-    _field.py
-    _trace.py
-    segy.py
-    tracefield.py
-    binfield.py
-    open.py
-    create.py
-    segysampleformat.py
-    tracesortingformat.py)
+        __init__.py
+        _header.py
+        _line.py
+        _field.py
+        _trace.py
+        segy.py
+        tracefield.py
+        binfield.py
+        open.py
+        create.py
+        segysampleformat.py
+        tracesortingformat.py)
 
 add_python_package(segyio segyio "${PYTHON_SOURCES}")
diff --git a/python/segyio/_depth_plane.py b/python/segyio/_depth_plane.py
deleted file mode 100644
index cccd173..0000000
--- a/python/segyio/_depth_plane.py
+++ /dev/null
@@ -1,18 +0,0 @@
-class DepthPlane:
-
-    def __init__(self, samples, sorting, read_fn):
-        self.samples = samples
-        self.sorting = sorting
-        self.read_fn = read_fn
-
-    def __getitem__(self, depth):
-        if isinstance(depth, int):
-            return self.read_fn(self.sorting, depth)
-        raise TypeError("Expected an int as index")
-
-    def __len__(self):
-        return len(self.samples)
-
-    def __iter__(self):
-        for i in xrange(self.samples):
-            yield self[i]
diff --git a/python/segyio/_line.py b/python/segyio/_line.py
index b20105e..341d1f9 100644
--- a/python/segyio/_line.py
+++ b/python/segyio/_line.py
@@ -62,9 +62,12 @@ class Line:
             except TypeError:
                 raise TypeError("Must be int or slice")
             else:
-                t0 = segyio._segyio.fread_trace0(lineno, len(self.other_lines), self.stride, self.lines, self.name)
+                t0 = self.trace0fn(lineno)
                 return self.readfn(t0, self.len, self.stride, buf)
 
+    def trace0fn(self, lineno):
+        return segyio._segyio.fread_trace0(lineno, len(self.other_lines), self.stride, self.lines, self.name)
+
     def __setitem__(self, lineno, val):
         if isinstance(lineno, slice):
             if lineno.start is None:
@@ -78,7 +81,7 @@ class Line:
 
             return
 
-        t0 = segyio._segyio.fread_trace0(lineno, len(self.other_lines), self.stride, self.lines, self.name)
+        t0 = self.trace0fn(lineno)
         self.writefn(t0, self.len, self.stride, val)
 
     def __len__(self):
diff --git a/python/segyio/segy.py b/python/segyio/segy.py
index fa2fcbc..77d4cfc 100644
--- a/python/segyio/segy.py
+++ b/python/segyio/segy.py
@@ -21,7 +21,6 @@ from segyio._header import Header
 from segyio._line import Line
 from segyio._trace import Trace
 from segyio._field import Field
-from segyio._depth_plane import DepthPlane
 import segyio._segyio as _segyio
 
 from segyio.tracesortingformat import TraceSortingFormat
@@ -312,30 +311,26 @@ class SegyFile(object):
         for i, v in itertools.izip(xrange(len(tr)), val):
             tr[i] = v
 
-    def _line_buffer(self, length, buf=None):
-        shape = (length, self.samples)
-
+    def _shape_buffer(self, shape, buf):
         if buf is None:
             return np.empty(shape=shape, dtype=np.single)
-
         if not isinstance(buf, np.ndarray):
             return buf
-
         if buf.dtype != np.single:
             return np.empty(shape=shape, dtype=np.single)
-
         if buf.shape[0] == shape[0]:
             return buf
-
         if buf.shape != shape and buf.size == np.prod(shape):
             return buf.reshape(shape)
-
         return buf
 
+    def _line_buffer(self, length, buf=None):
+        shape = (length, self.samples)
+        return self._shape_buffer(shape, buf)
+
     def _fread_line(self, trace0, length, stride, buf):
         return _segyio.read_line(self.xfd, trace0, length, stride, buf, self._tr0, self._bsz, self._fmt, self.samples)
 
-
     @property
     def ilines(self):
         """ :rtype: numpy.ndarray"""
@@ -497,28 +492,102 @@ class SegyFile(object):
     def xline(self, value):
         self.xline[:] = value
 
+    def _depth_buffer(self, buf=None):
+        il_len = self._iline_length
+        xl_len = self._xline_length
+
+        if self.sorting == TraceSortingFormat.CROSSLINE_SORTING:
+            shape = (xl_len, il_len)
+        elif self.sorting == TraceSortingFormat.INLINE_SORTING:
+            shape = (il_len, xl_len)
+        else:
+            raise RuntimeError("Unexpected sorting type")
+
+        return self._shape_buffer(shape, buf)
+
     @property
-    def depth_plane(self):
+    def depth_slice(self):
+        """ Interact with segy in depth slice mode.
+
+        This mode gives access to reading and writing functionality for depth slices.
+        The primary data type is the numpy ndarray. Depth slices can be accessed
+        individually or with python slices, and writing is done via assignment.
+        Note that each slice is returned as a numpy array, meaning
+        accessing the values of the slice is 0-indexed.
+
+        Examples:
+            Read a depth slice:
+                >>> il = f.depth_slice[199]
+
+            Copy every depth slice into a list::
+                >>> l = [np.copy(x) for x in f.depth_slice]
+
+            The number of depth slices in a file::
+                >>> len(f.depth_slice)
 
-        def readfn(sorting, depth):
-            il_len = self._iline_length
-            xl_len = self._xline_length
+            Numpy operations on every third depth slice::
+                >>> for depth_slice in f.depth_slice[::3]:
+                ...     depth_slice = depth_slice * 6
+                ...     avg = np.average(depth_slice)
+                ...     print(avg)
+                ...
+
+            Read depth_slices up to 250::
+                >>> for depth_slice in f.depth_slice[:250]:
+                ...     print(np.average(depth_slice))
+                ...
+
+            Copy a line from file g to f::
+                >>> f.depth_slice[4] = g.depth_slice[19]
 
-            if sorting == TraceSortingFormat.CROSSLINE_SORTING:
-                dim = (xl_len, il_len)
-            elif sorting == TraceSortingFormat.INLINE_SORTING:
-                dim = (il_len, xl_len)
-            else:
-                raise RuntimeError("Unexpected sorting type")
+            Copy lines from the first line in g to f, starting at 10,
+            ending at 49 in f::
+                >>> f.depth_slice[10:50] = g.depth_slice
+
+
+            Convenient way for setting depth slices, from left-to-right as the depth slices
+            numbers are specified in the file.depth_slice property, from an iterable
+            set on the right-hand-side.
+
+            If the right-hand-side depth slices are exhausted before all the destination
+            file depth slices the writing will stop, i.e. not all all depth slices in the
+            destination file will be written.
+
+            Copy depth slices from file f to file g::
+                >>> f.depth_slice = g.depth_slice.
+
+            Copy first half of the depth slices from g to f::
+                >>> f.depth_slice = g.depth_slice[:g.samples/2]]
+
+            Copy every other depth slices from a different file::
+                >>> f.depth_slice = g.depth_slice[::2]
+        """
+        indices = np.asarray(list(range(self.samples)), dtype=np.uintc)
+        other_indices = np.asarray([0], dtype=np.uintc)
+        buffn = self._depth_buffer
+
+        def readfn(depth, length, stride, buf):
+            buf_view = buf.reshape(self._iline_length * self._xline_length)
+
+            for i, trace_buf in enumerate(self.trace):
+                buf_view[i] = trace_buf[depth]
+
+            return buf
+
+        def writefn(depth, length, stride, val):
+            val = buffn(val)
 
-            plane = np.empty(shape=dim[0] * dim[1], dtype=np.single)
+            buf_view = val.reshape(self._iline_length * self._xline_length)
 
-            for i, t in enumerate(self.trace):
-                plane[i] = t[depth]
+            for i, trace_buf in enumerate(self.trace):
+                trace_buf[depth] = buf_view[i]
+                self.trace[i] = trace_buf
 
-            return plane.reshape(dim)
+        return Line(self, self.samples, 1, indices, other_indices, buffn, readfn, writefn, "Depth")
 
-        return DepthPlane(self.samples, self.sorting, readfn)
+    @depth_slice.setter
+    def depth_slice(self, value):
+        self.depth_slice[:] = value
 
     @property
     def text(self):
diff --git a/tests/test_segy.py b/tests/test_segy.py
index 80cbd83..636cbb5 100644
--- a/tests/test_segy.py
+++ b/tests/test_segy.py
@@ -8,7 +8,6 @@ from segyio import TraceField, BinField
 import shutil
 import filecmp
 
-from segyio._depth_plane import DepthPlane
 from segyio._field import Field
 from segyio._line import Line
 from segyio._header import Header
@@ -44,61 +43,6 @@ def mklines(fname):
             dst.header.xline[xl] = { TraceField.CROSSLINE_3D: xl }
 
 
-def make_file(filename, samples, first_iline, last_iline, first_xline, last_xline):
-
-    spec = segyio.spec()
-    # to create a file from nothing, we need to tell segyio about the structure of
-    # the file, i.e. its inline numbers, crossline numbers, etc. You can also add
-    # more structural information, but offsets etc. have sensible defaults. This is
-    # the absolute minimal specification for a N-by-M volume
-    spec.sorting = 2
-    spec.format = 1
-    spec.samples = samples
-    spec.ilines = range(*map(int, [first_iline, last_iline]))
-    spec.xlines = range(*map(int, [first_xline, last_xline]))
-
-    with segyio.create(filename, spec) as f:
-        start = 0.0
-        step = 0.00001
-        # fill a trace with predictable values: left-of-comma is the inline
-        # number. Immediately right of comma is the crossline number
-        # the rightmost digits is the index of the sample in that trace meaning
-        # looking up an inline's i's jth crosslines' k should be roughly equal
-        # to i.j0k
-        trace = np.arange(start = start,
-                          stop  = start + step * spec.samples,
-                          step  = step,
-                          dtype = np.float32)
-
-        # one inline is N traces concatenated. We fill in the xline number
-        line = np.concatenate([trace + (xl / 100.0) for xl in spec.xlines])
-
-        # write the line itself to the file
-        # write the inline number in all this line's headers
-        for ilno in spec.ilines:
-            f.iline[ilno] = (line + ilno)
-            f.header.iline[ilno] = { segyio.TraceField.INLINE_3D: ilno,
-                                     segyio.TraceField.offset: 1
-                                     }
-
-        # then do the same for xlines
-        for xlno in spec.xlines:
-            f.header.xline[xlno] = { segyio.TraceField.CROSSLINE_3D: xlno }
-
-
-def il_sample(s):
-    return int(s)
-
-
-def xl_sample(s):
-    return int(round((s-int(s))*100))
-
-
-def depth_sample(s):
-    return int(round((s - il_sample(s) - xl_sample(s)/100.0)*10e2,2)*100)
-
-
-
 class TestSegy(TestCase):
 
     def setUp(self):
@@ -167,36 +111,6 @@ class TestSegy(TestCase):
             # last sample
             self.assertAlmostEqual(5.22049, data[last_line, f.samples-1], places = 6)
 
-    def test_make_file(self):
-        filename = "test.segy"
-        samples = 10
-        make_file(filename, samples, 0, 2, 10, 13)
-
-        with segyio.open(filename, "r") as f:
-            for xlno, xl in itertools.izip(f.xlines, f.xline):
-                for ilno, trace in itertools.izip(f.ilines, xl):
-                    for sample_index, sample in itertools.izip(range(samples), trace):
-                        self.assertEqual(il_sample(sample), ilno,
-                                         ("sample: {0}, ilno {1}".format(il_sample(sample), ilno)))
-                        self.assertEqual(xl_sample(sample), xlno,
-                                         ("sample: {0}, xlno {1}, sample {2}".format(
-                                             xl_sample(sample), xlno, sample)))
-                        self.assertEqual(depth_sample(sample), sample_index,
-                                         ("sample: {0}, sample_index {1}, real_sample {2}".format(
-                                             depth_sample(sample), sample_index, sample)))
-
-    def test_read_all_depth_planes(self):
-        filename = "test.segy"
-        samples = 10
-        make_file(filename, samples, 0, 2, 10, 13)
-
-        with segyio.open(filename, "r") as f:
-            for i, plane in enumerate(f.depth_plane):
-                for ilno, xlno in itertools.product(range(len(f.ilines)), range(len(f.xlines))):
-                    self.assertEqual(depth_sample(plane[xlno, ilno]), i,
-                                     "plane[{0},{1}] == {2}, should be 0".format(
-                                         ilno, xlno, depth_sample(plane[xlno, ilno])))
-
     def test_iline_slicing(self):
         with segyio.open(self.filename, "r") as f:
             self.assertEqual(len(f.ilines), sum(1 for _ in f.iline))
@@ -557,8 +471,9 @@ class TestSegy(TestCase):
             self.assertIsInstance(f.tracecount, int)
             self.assertIsInstance(f.samples, int)
 
-            self.assertIsInstance(f.depth_plane, DepthPlane)
-            self.assertIsInstance(f.depth_plane[1], np.ndarray)
+            self.assertIsInstance(f.depth_slice, Line)
+            self.assertIsInstance(f.depth_slice[1], np.ndarray)
+            self.assertIsInstance(f.depth_slice[1:23], GeneratorType)
 
             self.assertIsInstance(f.ilines, np.ndarray)
             self.assertIsInstance(f.iline, Line)
@@ -590,4 +505,48 @@ class TestSegy(TestCase):
             self.assertIsInstance(f.trace[0], np.ndarray)
 
             self.assertIsInstance(f.bin, Field)
-            self.assertIsInstance(f.text, object) # inner TextHeader instance
+            self.assertIsInstance(f.text, object)  # inner TextHeader instance
+
+    def test_depth_slice_reading(self):
+        with segyio.open(self.filename, "r") as f:
+            self.assertEqual(len(f.depth_slice), f.samples)
+
+            for depth_sample in xrange(f.samples):
+                depth_slice = f.depth_slice[depth_sample]
+                self.assertIsInstance(depth_slice, np.ndarray)
+                self.assertEqual(depth_slice.shape, (5, 5))
+
+                for x, y in itertools.product(f.ilines, f.xlines):
+                    i, j = x - f.ilines[0], y - f.xlines[0]
+                    self.assertAlmostEqual(depth_slice[i][j], f.iline[x][j][depth_sample], places=6)
+
+            for index, depth_slice in enumerate(f.depth_slice):
+                self.assertIsInstance(depth_slice, np.ndarray)
+                self.assertEqual(depth_slice.shape, (5, 5))
+
+                for x, y in itertools.product(f.ilines, f.xlines):
+                    i, j = x - f.ilines[0], y - f.xlines[0]
+                    self.assertAlmostEqual(depth_slice[i][j], f.iline[x][j][index], places=6)
+
+        with self.assertRaises(KeyError):
+            slice = f.depth_slice[f.samples]
+
+    def test_depth_slice_writing(self):
+        fname = self.filename.replace("small", "small-depth")
+        shutil.copyfile(self.filename, fname)
+
+        buf = np.empty(shape=(5, 5), dtype=np.single)
+
+        def value(x, y):
+            return x + (1.0 / 5) * y
+
+        for x, y in itertools.product(range(5), range(5)):
+            buf[x][y] = value(x, y)
+
+        with segyio.open(fname, "r+") as f:
+            f.depth_slice[7] = buf * 3.14 # assign to depth 7
+            self.assertTrue(np.allclose(f.depth_slice[7], buf * 3.14))
+
+            f.depth_slice = [buf * i for i in xrange(len(f.depth_slice))]  # assign to all depths
+            for index, depth_slice in enumerate(f.depth_slice):
+                self.assertTrue(np.allclose(depth_slice, buf * index))
diff --git a/tests/test_segyio_c.py b/tests/test_segyio_c.py
index 6035c58..bd73b6b 100644
--- a/tests/test_segyio_c.py
+++ b/tests/test_segyio_c.py
@@ -423,3 +423,15 @@ class _segyioTests(TestCase):
         self.assertAlmostEqual(sum(sum(buf)), 305.061146736, places=6)
 
         _segyio.close(f)
+
+    def test_fread_trace0_for_depth(self):
+        elements = list(range(25))
+        indices = numpy.asarray(elements, dtype=numpy.uintc)
+
+        for index in indices:
+            d = _segyio.fread_trace0(index, 1, 1, indices, "depth")
+            self.assertEqual(d, index)
+
+        with self.assertRaises(KeyError):
+            d = _segyio.fread_trace0(25, 1, 1, indices, "depth")
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/segyio.git



More information about the debian-science-commits mailing list