[segyio] 02/376: Added GUI segyviewer as an example

Jørgen Kvalsvik jokva-guest at moszumanska.debian.org
Wed Sep 20 08:03:57 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 64e94c17d79fe995f7848a3cdeced39dc66411d5
Author: Thorvald Johannessen <thorvjo at statoil.com>
Date:   Mon Oct 3 12:15:44 2016 +0200

    Added GUI segyviewer as an example
    
    And added depth_plane functionality in python layer
---
 examples/CMakeLists.txt |   2 +
 examples/segyviewer.py  | 387 ++++++++++++++++++++++++++++++++++++++++++++++++
 python/segyio/segy.py   |  47 ++++++
 tests/test_segy.py      |  88 ++++++++++-
 4 files changed, 522 insertions(+), 2 deletions(-)

diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 46036bb..2c1f261 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,6 +1,8 @@
 configure_file(../tests/test-data/small.sgy test-data/small.sgy COPYONLY)
+configure_file(segyviewer.py segyviewer.py COPYONLY)
 
 add_python_example(python.examples.about about.py test-data/small.sgy INLINE_3D CROSSLINE_3D)
 add_python_example(python.examples.write write.py test-data/small.sgy)
 add_python_example(python.examples.makefile make-file.py test-data/large-file.sgy 20 1 20 1 20)
 add_python_example(python.examples.subcube copy-sub-cube.py test-data/small.sgy test-data/copy.sgy)
+
diff --git a/examples/segyviewer.py b/examples/segyviewer.py
new file mode 100644
index 0000000..369a93e
--- /dev/null
+++ b/examples/segyviewer.py
@@ -0,0 +1,387 @@
+from PyQt4.QtGui import QAction
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+import segyio
+
+from pylab import *
+
+from PyQt4 import QtGui, QtCore
+
+from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.figure import Figure
+
+import matplotlib.patches as patches
+
+
+class LineSelectionMonitor(QObject):
+    ilineChanged = pyqtSignal(int)
+    xlineChanged = pyqtSignal(int)
+    depthChanged = pyqtSignal(int)
+
+    def __init__(self, parent=None):
+        QObject.__init__(self, parent)
+
+    def ilineUpdated(self, new_index):
+        print("iline:{0} updated", new_index)
+        self.ilineChanged.emit(new_index)
+
+    def xlineUpdated(self, new_index):
+        print("xline:{0} updated", new_index)
+        self.xlineChanged.emit(new_index)
+
+    def depthUpdated(self, new_index):
+        print("depth:{0} updated", new_index)
+        self.depthChanged.emit(new_index)
+
+
+class ColorMapMonitor(QObject):
+    cmap_changed = pyqtSignal(str)
+
+    def __init__(self, parent=None):
+        QObject.__init__(self, parent)
+
+    def colormapUpdated(self, value):
+        self.cmap_changed.emit(str(value))
+
+
+class PlotCanvas(FigureCanvas):
+    """
+    Generic plot canvas for all plane views
+    """
+
+    indexChanged = pyqtSignal(int)
+
+    def __init__(self, planes, indexes, dataset_title, cmap, display_horizontal_indicator=False,
+                 display_vertical_indicator=False, parent=None, width=800, height=100, dpi=20):
+
+        self.planes = planes
+        self.indexes = indexes
+
+        self.dataset_title = dataset_title
+
+        self.fig = Figure(figsize=(width, height), dpi=dpi, facecolor='white')
+        FigureCanvas.__init__(self, self.fig)
+
+        self.setParent(parent)
+
+        self.axes = self.fig.add_subplot(111)
+        # self.axes.set_title(self.dataset_title, fontsize=50)
+        self.axes.set_xticks(indexes)
+        self.axes.tick_params(axis='both', labelsize=30)
+
+        # the default colormap
+        self.cmap = cmap
+        self.im = self.axes.imshow(planes[indexes[0]].T, interpolation="nearest", aspect="auto", cmap=self.cmap)
+
+        # initialize plot and indicator_rect
+        # self.plot_image(self.planes[self.indexes[0]])
+
+        self.current_index = 0
+
+        # connecting matplotlib mouse signals
+        self.mpl_connect('motion_notify_event', self.mouse_moved)
+        self.mpl_connect('button_press_event', self.mouse_clicked)
+        self.mpl_connect('axes_leave_event', self.mouse_left)
+
+        if display_vertical_indicator:
+            self.verdical_indicator_rect = self.axes.add_patch(
+                patches.Rectangle(
+                    (-0.5, 0),  # (x,y)
+                    1,  # width - bredde er pr dot ikke pixel...
+                    len(self.planes[self.indexes[0]][0]),  # height / depth
+                    fill=False,
+                    alpha=1,
+                    color='black',
+                    linestyle='--',
+                    linewidth=2
+                )
+            )
+
+        if display_horizontal_indicator:
+            self.horizontal_indicator_rect = self.axes.add_patch(
+                patches.Rectangle(
+                    (-0.5, 0),  # (x,y)
+                    len(self.planes[self.indexes[0]][0]),  # width - bredde er pr dot ikke pixel...
+                    1,  # height / depth
+                    fill=False,
+                    alpha=1,
+                    color='black',
+                    linestyle='--',
+                    linewidth=2
+                )
+            )
+
+        self.disabled_overlay = self.axes.add_patch(
+            patches.Rectangle(
+                (-0.5, 0),  # (x,y)
+                len(self.planes[self.indexes[0]][0]),  # width - bredde er pr dot ikke pixel...
+                len(self.planes[self.indexes[0]][0]),  # height / depth
+                alpha=0.5,
+                color='gray',
+                visible=False
+            )
+        )
+
+        # initialize plot
+        print(self.dataset_title, self.cmap)
+
+        # self.plot_image(planes[indexes[0]])
+
+    def mouse_left(self, evt):
+        pass
+        # self.set_vertical_line_indicator(self.current_index)
+
+    def mouse_clicked(self, evt):
+        if evt.inaxes:
+            self.current_index = int(evt.xdata)
+            self.indexChanged.emit(self.indexes[int(evt.xdata)])
+
+    def mouse_moved(self, evt):
+        # do nothing
+        # if evt.inaxes:
+        #   self.set_vertical_line_indicator(int(evt.xdata))
+        pass
+
+    def set_colormap(self, cmap):
+        self.cmap = cmap
+        self.im.set_cmap(cmap)
+        self.draw()
+
+    def update_image(self, index):
+        self.im.set_data(self.planes[index].T)
+        self.draw()
+
+    def set_vertical_line_indicator(self, line_index):
+        self.verdical_indicator_rect.set_x(line_index - 0.5)
+        self.verdical_indicator_rect.set_y(0)
+        self.draw()
+
+    def set_horizontal_line_indicator(self, line_index):
+        self.horizontal_indicator_rect.set_x(-0.5)
+        self.horizontal_indicator_rect.set_y(line_index)
+        self.draw()
+
+    def enable_overlay(self):
+        self.disabled_overlay.set_visible(True)
+        self.draw()
+
+    def disable_overlay(self):
+        self.disabled_overlay.set_visible(False)
+        self.draw()
+
+
+class PlotWidget(QtGui.QWidget):
+    """
+    Main widget holding the figure and slider
+    """
+
+    # indexChanged = pyqtSignal(int)
+
+    def __init__(self, planes, indexes, dataset_title, default_cmap='seismic',
+                 show_h_indicator=False, show_v_indicator=False):
+        super(PlotWidget, self).__init__()
+
+        self.planes = planes
+        self.indexes = indexes
+        self.dataset_title = dataset_title
+        self.default_cmap = default_cmap
+        self.show_h_indicator = show_h_indicator
+        self.show_v_indicator = show_v_indicator
+        self.setAutoFillBackground(True)
+        p = self.palette()
+        p.setColor(self.backgroundRole(), Qt.white)
+        self.setPalette(p)
+
+        self.plotCanvas = PlotCanvas(self.planes, self.indexes, self.dataset_title, self.default_cmap,
+                                     display_horizontal_indicator=self.show_h_indicator,
+                                     display_vertical_indicator=self.show_v_indicator)
+
+        self.layout = QtGui.QVBoxLayout(self)
+        self.layout.addWidget(self.plotCanvas)
+
+    def set_cmap(self, action):
+        self.plotCanvas.set_colormap(str(action))
+
+    def set_vertical_line_indicator(self, line):
+        print("set vertical line ind:", line)
+        self.plotCanvas.set_vertical_line_indicator(line)
+
+    def set_horizontal_line_indicator(self, line):
+        print("set horizontal line ind:", line)
+        self.plotCanvas.set_horizontal_line_indicator(line)
+
+
+class TopMenu(QMenuBar):
+    def __init__(self, parent, colormap_monitor):
+        super(QMenuBar, self).__init__(parent)
+
+        self.fileMenu = self.addMenu('&File')
+        exitAction = QtGui.QAction('&Exit', self)
+        self.fileMenu.addAction(exitAction)
+
+        self.viewMenu = self.addMenu('&View')
+        self.colormapMenu = self.viewMenu.addMenu("&Colormap")
+
+        self.colormap_monitor = colormap_monitor
+
+        self.colormap_monitor.cmap_changed.connect(self.set_selected_cmap)
+
+        def colormapChanger(color_map_name):
+            def performColorMapChange():
+                self.colormap_monitor.colormapUpdated(color_map_name)
+
+            return performColorMapChange
+
+        for item in ['seismic', 'spectral', 'RdGy', 'hot', 'jet', 'gray']:
+            action = self.colormapMenu.addAction(item)
+            action.setCheckable(True)
+            action.triggered.connect(colormapChanger(item))
+
+    def set_selected_cmap(self, cmap_name):
+        for item in self.colormapMenu.actions():
+            item.setChecked(str(item.text()) == cmap_name)
+
+
+class LineSelector(QtGui.QWidget):
+    indexChanged = pyqtSignal(int)
+
+    def __init__(self, parent, label, indexes, monitor_func):
+        super(QtGui.QWidget, self).__init__(parent)
+        self.label = label
+        self.indexes = indexes
+        self.monitor_func = monitor_func
+
+        self.layout = QHBoxLayout()
+        self.slabel = QLabel(self.label)
+        self.sbox = QtGui.QSpinBox(self)
+        self.sbox.setRange(self.indexes[0], self.indexes[-1])
+        self.sbox.valueChanged.connect(self.monitor_func)
+        self.layout.addWidget(self.slabel)
+        self.layout.addWidget(self.sbox)
+        self.setLayout(self.layout)
+
+    def index_changed(self, val):
+        self.indexChanged.emit(self.indexes[val])
+
+    def set_index(self, val):
+        self.sbox.setValue(val)
+
+
+class ToolBar(QToolBar):
+    def __init__(self, xline_indexes, iline_indexes, depth_indexes, line_selection_monitor):
+        super(ToolBar, self).__init__("")
+        self.xline_indexes = xline_indexes
+        self.iline_indexes = iline_indexes
+        self.depth_indexes = depth_indexes
+        self.line_selection_monitor = line_selection_monitor
+
+        # xline
+        self.xline_selector = LineSelector(self, "xline", self.xline_indexes, self.line_selection_monitor.xlineUpdated)
+        self.line_selection_monitor.xlineChanged.connect(self.xline_selector.set_index)
+        self.addWidget(self.xline_selector)
+
+        # iline
+        self.iline_selector = LineSelector(self, "iline", self.iline_indexes, self.line_selection_monitor.ilineUpdated)
+        self.line_selection_monitor.ilineChanged.connect(self.iline_selector.set_index)
+        self.addWidget(self.iline_selector)
+
+        # iline
+
+        self.depth_selector = LineSelector(self, "depth", self.depth_indexes, self.line_selection_monitor.depthUpdated)
+        self.addWidget(self.depth_selector)
+
+
+class AppWindow(QtGui.QMainWindow):
+    def __init__(self, s):
+        QtGui.QMainWindow.__init__(self)
+        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+        self.setWindowTitle("Segy Viewer")
+        self.main_widget = QtGui.QWidget(self)
+
+        # signal monitors
+        colormap_monitor = ColorMapMonitor(self)
+        line_monitor = LineSelectionMonitor(self)
+
+        self.setMenuBar(TopMenu(self, colormap_monitor))
+        self.addToolBar(ToolBar(s.xlines, s.ilines, range(s.samples), line_monitor))
+        self.statusBar()
+
+        # depth = s.samples
+        # depth_plane = []  # [ : for s.xline[:,depth] in s.xline]
+        # all_traces = np.empty(shape=((len(s.ilines) * len(s.xlines)), s.samples), dtype=np.float32)
+        #
+        # for i, t in enumerate(s.trace):
+        #     all_traces[i] = t
+        #
+        # all_traces2 = all_traces.reshape(len(s.ilines), len(s.xlines), s.samples)
+        #
+        # all_traces3 = all_traces2.transpose(2, 0, 1)
+
+
+
+        # initialize
+        x_plane_canvas = PlotWidget(s.xline, s.xlines, "xlines", show_v_indicator=True)
+        i_plane_canvas = PlotWidget(s.iline, s.ilines, "ilines", show_v_indicator=True)
+        depth_plane_canvas = PlotWidget(s.depth_plane, range(s.samples), "depth",
+                                        show_v_indicator=True, show_h_indicator=True)
+
+        # attach signals
+        x_plane_canvas.plotCanvas.indexChanged.connect(line_monitor.ilineUpdated)
+        i_plane_canvas.plotCanvas.indexChanged.connect(line_monitor.xlineUpdated)
+
+        line_monitor.ilineChanged.connect(x_plane_canvas.set_vertical_line_indicator)
+        line_monitor.ilineChanged.connect(depth_plane_canvas.set_horizontal_line_indicator)
+        line_monitor.ilineChanged.connect(i_plane_canvas.plotCanvas.update_image)
+
+        line_monitor.xlineChanged.connect(i_plane_canvas.set_vertical_line_indicator)
+        line_monitor.xlineChanged.connect(depth_plane_canvas.set_vertical_line_indicator)
+        line_monitor.xlineChanged.connect(x_plane_canvas.plotCanvas.update_image)
+
+        line_monitor.depthChanged.connect(depth_plane_canvas.plotCanvas.update_image)
+
+        # colormap signals
+        colormap_monitor.cmap_changed.connect(x_plane_canvas.set_cmap)
+        colormap_monitor.cmap_changed.connect(i_plane_canvas.set_cmap)
+        colormap_monitor.cmap_changed.connect(depth_plane_canvas.set_cmap)
+
+        # layout
+        xdock = QDockWidget("x-lines")
+        xdock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
+        xdock.setWidget(x_plane_canvas)
+
+        idock = QDockWidget("i-lines")
+        idock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
+        idock.setWidget(i_plane_canvas)
+
+        ddock = QDockWidget("depth plane")
+        ddock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
+        ddock.setWidget(depth_plane_canvas)
+
+        self.setDockOptions(QMainWindow.AllowNestedDocks)
+        self.addDockWidget(Qt.TopDockWidgetArea, xdock)
+        self.addDockWidget(Qt.BottomDockWidgetArea, idock)
+        self.addDockWidget(Qt.BottomDockWidgetArea, ddock)
+
+        self.main_widget.setFocus()
+        self.setCentralWidget(self.main_widget)
+        self.main_widget.hide()
+
+
+def main():
+    if len(sys.argv) < 2:
+        sys.exit("Usage: segyviewer.py [file]")
+
+    filename = sys.argv[1]
+
+    # read the file given by cmd arg
+    with segyio.open(filename, "r") as s:
+        qApp = QtGui.QApplication(sys.argv)
+        aw = AppWindow(s)
+        aw.show()
+        sys.exit(qApp.exec_())
+
+
+if __name__ == '__main__':
+    main()
diff --git a/python/segyio/segy.py b/python/segyio/segy.py
index 1ef6c59..24e842f 100644
--- a/python/segyio/segy.py
+++ b/python/segyio/segy.py
@@ -171,6 +171,27 @@ class _line:
         for i in self.lines:
             yield self.__getitem__(i, buf)
 
+
+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)
+
+    def __len__(self):
+        return len(self.samples)
+
+    def __iter__(self):
+        for i in range(self.samples):
+            yield self.__getitem__(i)
+
+
 class _header:
     def __init__(self, segy):
         self.segy = segy
@@ -1036,6 +1057,31 @@ class file(BaseCClass):
     def xline(self, value):
         self.xline[:] = value
 
+    @property
+    def depth_plane(self):
+
+        def depth_plane(sorting, depth):
+            il_len = self._iline_length
+            xl_len = self._xline_length
+
+            dim = None
+            if sorting == 1:
+                dim = (xl_len, il_len)
+            if sorting == 2:
+                dim = (il_len, xl_len)
+
+            if not dim:
+                raise Exception("TODO")
+
+            plane = np.empty(shape=dim[0] * dim[1], dtype=np.float32)
+
+            for i, t in enumerate(self.trace):
+                plane[i] = t[depth]
+
+            return plane.reshape(dim)
+
+        return _DepthPlane(self.samples, self.sorting, depth_plane)
+
     def _readh(self, index, buf = None):
         errc = self._read_header(index, buf, self._tr0, self._bsz)
         err = _error(errc)
@@ -1300,3 +1346,4 @@ class spec:
         self.format         = None
         self.sorting        = None
         self.t0             = 1111.0
+        self.depth_plane    = None
diff --git a/tests/test_segy.py b/tests/test_segy.py
index 96b2cb0..4693240 100644
--- a/tests/test_segy.py
+++ b/tests/test_segy.py
@@ -3,9 +3,9 @@ from unittest import TestCase
 import segyio
 from segyio import TraceField, BinField
 import shutil
-import sys
-import time
 import filecmp
+import itertools
+
 
 class TestSegy(TestCase):
 
@@ -75,6 +75,90 @@ class TestSegy(TestCase):
             # last sample
             self.assertAlmostEqual(5.22049, data[last_line, f.samples-1], places = 6)
 
+    @staticmethod
+    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 }
+
+    @staticmethod
+    def il_sample(s):
+        return int(s)
+
+    @staticmethod
+    def xl_sample(s):
+        return int(round((s-int(s))*100))
+
+    @classmethod
+    def depth_sample(cls, s):
+        return int(round((s - cls.il_sample(s) - cls.xl_sample(s)/100.0)*10e2,2)*100)
+
+    def test_make_file(self):
+        filename = "test.segy"
+        samples = 10
+        self.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(self.il_sample(sample), ilno,
+                                         ("sample: {0}, ilno {1}".format(self.il_sample(sample), ilno)))
+                        self.assertEqual(self.xl_sample(sample), xlno,
+                                         ("sample: {0}, xlno {1}, sample {2}".format(
+                                             self.xl_sample(sample), xlno, sample)))
+                        self.assertEqual(self.depth_sample(sample), sample_index,
+                                         ("sample: {0}, sample_index {1}, real_sample {2}".format(
+                                             self.depth_sample(sample), sample_index, sample)))
+
+    def test_read_all_depth_planes(self):
+        filename = "test.segy"
+        samples = 10
+        self.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(self.depth_sample(plane[xlno, ilno]), i,
+                                     "plane[{0},{1}] == {2}, should be 0".format(
+                                         ilno, xlno, self.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))

-- 
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