Read OpendTect horizons#

The best way to export horizons from OpendTect is with these options:

  • x/y and inline/crossline

  • with header (single or multi-line, it doesn’t matter)

  • choose all the attributes you want

On the last point, if you choose multiple horizons in one file, you can only have one attribute in the file.

IL/XL only, single-line header, multiple attributes#

import gio

ds = gio.read_odt('../../data/OdT/3d_horizon/Segment_ILXL_Single-line-header.dat')
ds
<xarray.Dataset>
Dimensions:         (iline: 54, xline: 57)
Coordinates:
  * iline           (iline) int64 376 378 380 382 384 ... 474 476 478 480 482
  * xline           (xline) int64 812 814 816 818 820 ... 916 918 920 922 924
Data variables:
    twt             (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    amplitude       (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    correlation     (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    seed_index      (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    tracking_order  (iline, xline) float64 nan nan nan nan ... nan nan nan nan
Attributes:
    odt_filename:  ../../data/OdT/3d_horizon/Segment_ILXL_Single-line-header.dat
ds['twt'].plot()
<matplotlib.collections.QuadMesh at 0x7f06f7414910>
../_images/df26d5122ed01726f0b0c2d0156457233b09d62f69fb21aae3744016cb4d7f40.png

IL/XL and XY, multi-line header, multiple attributes#

Load everything (default)#

X and Y are loaded as cdp_x and cdp_y, to be consistent with the seisnc standard in segysak.

ds = gio.read_odt('../../data/OdT/3d_horizon/Segment_XY-and-ILXL_Multi-line-header.dat')
ds
<xarray.Dataset>
Dimensions:         (iline: 54, xline: 57)
Coordinates:
  * iline           (iline) int64 376 378 380 382 384 ... 474 476 478 480 482
  * xline           (xline) int64 812 814 816 818 820 ... 916 918 920 922 924
    cdp_x           (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    cdp_y           (iline, xline) float64 nan nan nan nan ... nan nan nan nan
Data variables:
    twt             (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    amplitude       (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    correlation     (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    seed_index      (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    tracking_order  (iline, xline) float64 nan nan nan nan ... nan nan nan nan
Attributes:
    odt_filename:  ../../data/OdT/3d_horizon/Segment_XY-and-ILXL_Multi-line-h...
import matplotlib.pyplot as plt

plt.scatter(ds.coords['cdp_x'], ds.coords['cdp_y'], s=5)
<matplotlib.collections.PathCollection at 0x7f06f535de50>
../_images/00b37cd7dd4c24c7ed82d64d7d76f2d26c44512016322dfa719347f311bd9a48.png

Load only inline, crossline, TWT#

There is only one attribute here: Z, which is the two-way time of the horizon.

Note that when loading data from OpendTect, you always get an xarray.Dataset, even if there’s only a single attribute. This is because the format supports multiple grids and we didn’t want you to have to guess what a given file would produce.

fname = '../../data/OdT/3d_horizon/Segment_XY-and-ILXL_Multi-line-header.dat'
names = ['Inline', 'Crossline', 'Z']  # Must match OdT DAT file.

ds = gio.read_odt(fname, names=names)
ds
<xarray.Dataset>
Dimensions:  (iline: 54, xline: 57)
Coordinates:
  * iline    (iline) int64 376 378 380 382 384 386 ... 472 474 476 478 480 482
  * xline    (xline) int64 812 814 816 818 820 822 ... 914 916 918 920 922 924
Data variables:
    twt      (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    odt_filename:  ../../data/OdT/3d_horizon/Segment_XY-and-ILXL_Multi-line-h...

XY only#

If you have a file with no IL/XL, gio can try to load data using only X and Y:

  • If there’s a header you can load any number of attributes.

  • If there’s no header, you can only one attribute (e.g. TWT) automagically…

  • OR, if there’s no header, you can provide names to tell gio what everything is.

gio must create fake inline and crossline numbers; you can provide an origin and a step size. For example, notice above that the true inline and crossline numbers are:

  • inline: 376, 378, 380, etc.

  • crossline: 812, 814, 816, etc.

So we can pass an origin of (376, 812) and a step of (2, 2) to mimic these.

Header present#

fname = '../../data/OdT/3d_horizon/Segment_XY_Single-line-header.dat'

ds = gio.read_odt(fname, origin=(376, 812), step=(2, 2))
ds
/home/runner/.local/lib/python3.8/site-packages/gio/opendtect.py:244: UserWarning: Attempting to construct grid from (x, y) locations.
  dx = df_to_xarray(df, attrs=attrs, origin=origin, step=step)
<xarray.Dataset>
Dimensions:         (iline: 54, xline: 57)
Coordinates:
  * iline           (iline) int64 376 378 380 382 384 ... 474 476 478 480 482
  * xline           (xline) int64 812 814 816 818 820 ... 916 918 920 922 924
    cdp_x           (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    cdp_y           (iline, xline) float64 nan nan nan nan ... nan nan nan nan
Data variables:
    twt             (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    amplitude       (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    correlation     (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    seed_index      (iline, xline) float64 nan nan nan nan ... nan nan nan nan
    tracking_order  (iline, xline) float64 nan nan nan nan ... nan nan nan nan
Attributes:
    odt_filename:  ../../data/OdT/3d_horizon/Segment_XY_Single-line-header.dat
ds['twt'].plot()
<matplotlib.collections.QuadMesh at 0x7f06f52b6f70>
../_images/df26d5122ed01726f0b0c2d0156457233b09d62f69fb21aae3744016cb4d7f40.png

No header, more than one attribute: raises an error#

fname = '../../data/OdT/3d_horizon/Segment_XY_No-header.dat'

ds = gio.read_odt(fname)
ds

# Raises an error:
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [8], line 3
      1 fname = '../../data/OdT/3d_horizon/Segment_XY_No-header.dat'
----> 3 ds = gio.read_odt(fname)
      4 ds

File ~/.local/lib/python3.8/site-packages/gio/opendtect.py:222, in read_odt(fname, names, usecols, na_values, origin, step, attrs)
    170 def read_odt(fname,
    171              names=None,
    172              usecols=None,
   (...)
    176              attrs=None
    177              ):
    178     """
    179     Read an OdT horizon file. (Native format, not IESX.)
    180 
   (...)
    220             crossline — and cdp_x and cdp_y, if present — will be coordinates.
    221     """
--> 222     df = read_odt_as_df(fname,
    223                         names=names,
    224                         usecols=usecols,
    225                         na_values=na_values
    226                         )
    228     attrs = attrs or {}
    229     attrs.update({'odt_filename': fname})

File ~/.local/lib/python3.8/site-packages/gio/opendtect.py:96, in read_odt_as_df(fname, names, usecols, na_values, **kwargs)
     94 else:
     95     message = 'First two columns must be integers to be interpreted as inline and crossline.'
---> 96     raise TypeError(message)
     97 cols = range(len(data_cols))
     98 names = ix + [f'var_{n}' for n in cols if n in (usecols or cols)]

TypeError: First two columns must be integers to be interpreted as inline and crossline.
fname = '../../data/OdT/3d_horizon/Segment_XY_No-header.dat'

ds = gio.read_odt(fname, names=['X', 'Y', 'TWT'])
ds
/home/matt/miniconda3/envs/py39/lib/python3.9/site-packages/gio/opendtect.py:244: UserWarning: Attempting to construct grid from (x, y) locations.
  dx = df_to_xarray(df, attrs=attrs, origin=origin, step=step)
<xarray.Dataset>
Dimensions:  (iline: 54, xline: 57)
Coordinates:
  * iline    (iline) int64 0 1 2 3 4 5 6 7 8 9 ... 44 45 46 47 48 49 50 51 52 53
  * xline    (xline) int64 0 1 2 3 4 5 6 7 8 9 ... 47 48 49 50 51 52 53 54 55 56
    cdp_x    (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan nan
    cdp_y    (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan nan
Data variables:
    twt      (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    odt_filename:  ../../data/OdT/3d_horizon/Segment_XY_No-header.dat
ds['twt'].plot()
<matplotlib.collections.QuadMesh at 0x7f29b70b1730>
../_images/50e48124720faa52e0f9576aa8bd16f6ab450622c81f9a3d221f3b3d4d6d9536.png

Sparse data#

Sometimes a surface only exists at a few points, e.g. a 3D seismic interpretation grid. In general, loading data like this is completely safe if you have inline and xline locations. If you only have (x, y) locations, gio will attempt to load it, but you should inspect the result carefullly.

fname = '../../data/OdT/3d_horizon/Nimitz_Salmon_XY-and-ILXL_Single-line-header.dat'

ds = gio.read_odt(fname)
ds
<xarray.Dataset>
Dimensions:    (iline: 62, xline: 185)
Coordinates:
  * iline      (iline) int64 1190 1197 1198 1199 1200 ... 1254 1255 1256 1257
  * xline      (xline) int64 1941 1942 1943 1944 1945 ... 2122 2123 2124 2125
    cdp_x      (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan
    cdp_y      (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan
Data variables:
    twt        (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan
    amplitude  (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan
Attributes:
    odt_filename:  ../../data/OdT/3d_horizon/Nimitz_Salmon_XY-and-ILXL_Single...
ds['twt'].plot.imshow()
<matplotlib.image.AxesImage at 0x7f29b6fc0df0>
../_images/0173f6492e208729eb2a85720028d85820fc2272e800a20a127339887ba75104.png

There’s some sort of artifact with the default plot style, which uses pcolormesh I think.

ds['twt'].plot()
<matplotlib.collections.QuadMesh at 0x7f29b6efc550>
../_images/336eef517fd16752c7dce2c9b4b27f80a04664afb1b6fccd116ddd610b5037b2.png

Multiple horizons in one file#

You can export multiple horizons from OpendTect. These will be loaded as one xarray.Dataset as different Data variables. (The actual attribute you exported from OdT is always called Z; this information is not retained in the xarray.)

fname = '../../data/OdT/3d_horizon/F3_Multi-H2-H4_ILXL_Single-line-header.dat'

ds = gio.read_odt(fname)
ds
<xarray.Dataset>
Dimensions:               (xline: 93, iline: 45)
Coordinates:
  * xline                 (xline) int64 964 966 968 970 ... 1142 1144 1146 1148
  * iline                 (iline) int64 110 112 114 116 118 ... 192 194 196 198
Data variables:
    F3_Demo_2_FS6         (iline, xline) float64 nan nan nan ... 825.4 823.9
    F3_Demo_4_Truncation  (iline, xline) float64 820.0 820.7 ... 635.3 635.2
ds['F3_Demo_2_FS6'].plot()
<matplotlib.collections.QuadMesh at 0x7f29b6e580a0>
../_images/391e264707d5801b066dad7e43a18ecb1520e5b695cb91f68cf34ba4917c8df7.png
ds['F3_Demo_4_Truncation'].plot()
<matplotlib.collections.QuadMesh at 0x7f29b6d7ddc0>
../_images/782c22fa9e7259c83a8b2c89c198ba2ede855dba7d00bab8ed7098e009baa1ad.png

Multi-horizon, no header#

Unfortunately, OdT exports (x, y) in the first two columns, meaning you can’t assume that columns 3 and 4 are inline, crossline. So if there’s no header, and XY as well as inline/xline, you have to give the column names:

import gio

fname = '../../data/OdT/3d_horizon/Test_Multi_XY-and-ILXL_Z-only.dat'

ds = gio.read_odt(fname, names=['Horizon', 'X', 'Y', 'Inline', 'Crossline', 'Z'])
ds
<xarray.Dataset>
Dimensions:      (iline: 1, xline: 10)
Coordinates:
  * iline        (iline) int64 990
  * xline        (xline) int64 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
    cdp_x        (iline, xline) float64 1.703e+06 1.703e+06 ... 1.703e+06
    cdp_y        (iline, xline) float64 5.817e+06 5.817e+06 ... 5.818e+06
Data variables:
    blue event   (iline, xline) float64 1.29e+03 1.293e+03 ... 1.305e+03
    green event  (iline, xline) float64 1.29e+03 1.293e+03 ... 1.305e+03

Undefined values#

These are exported as '1e30' by default. You can override this (not add to it, which is the default pandas behaviour) by passing one or more na_values.

fname = '../../data/OdT/3d_horizon/Segment_XY_No-header_NULLs.dat'

ds = gio.read_odt(fname, names=['X', 'Y', 'TWT'])
ds
/home/matt/miniconda3/envs/py39/lib/python3.9/site-packages/gio/opendtect.py:244: UserWarning: Attempting to construct grid from (x, y) locations.
  dx = df_to_xarray(df, attrs=attrs, origin=origin, step=step)
<xarray.Dataset>
Dimensions:  (iline: 54, xline: 57)
Coordinates:
  * iline    (iline) int64 0 1 2 3 4 5 6 7 8 9 ... 44 45 46 47 48 49 50 51 52 53
  * xline    (xline) int64 0 1 2 3 4 5 6 7 8 9 ... 47 48 49 50 51 52 53 54 55 56
    cdp_x    (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan nan
    cdp_y    (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan nan
Data variables:
    twt      (iline, xline) float64 nan nan nan nan nan ... nan nan nan nan nan
Attributes:
    odt_filename:  ../../data/OdT/3d_horizon/Segment_XY_No-header_NULLs.dat
ds['twt'].plot()
<matplotlib.collections.QuadMesh at 0x7f29b719da00>
../_images/40b6c79d8a48ba313c33b6acf1ba2ce058bd3bf390f8e002b9c490ba871a8309.png

Writing OdT files#

You can write an OdT file using gio.to_odt():

_ = gio.to_odt('out.dat', ds)
!head out.dat
# 1: Inline
# 2: Crossline
# 3: X
# 4: Y
# 5: Z
# - - - - - - - - - -
0	51	620986.88	6080882.36	648.6175060272217
0	52	621036.86	6080883.76	647.8394865989685
0	53	621086.84	6080885.16	647.9663848876953
1	50	620935.5	6080930.95	649.9454379081726