[segyio] 01/376: Initial commit.

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 5c05b1bcd9d84da7d383ef03a2f8739a2aaff1f6
Author: Jørgen Kvalsvik <jokva at statoil.com>
Date:   Wed Sep 28 13:53:46 2016 +0200

    Initial commit.
---
 .gitlab-ci.yml                      |   12 +
 CMakeLists.txt                      |   54 ++
 License.md                          |  157 +++++
 README.md                           |  113 +++
 cmake/matlab.cmake                  |  257 +++++++
 cmake/python.cmake                  |   57 ++
 cmake/test_runner.py                |   48 ++
 examples/CMakeLists.txt             |    6 +
 examples/about.py                   |   49 ++
 examples/copy-sub-cube.py           |   41 ++
 examples/make-file.py               |   55 ++
 examples/segyview.py                |   32 +
 examples/write.py                   |   84 +++
 mex/CMakeLists.txt                  |   40 ++
 mex/Segy.m                          |  550 +++++++++++++++
 mex/SegySampleFormat.m              |   13 +
 mex/SegySpec.m                      |   48 ++
 mex/TraceField.m                    |   96 +++
 mex/TraceSortingFormat.m            |    9 +
 mex/get_line.c                      |   41 ++
 mex/segy_get_bfield_mex.c           |   24 +
 mex/segy_get_field_mex.c            |   24 +
 mex/segy_get_header_mex.c           |   61 ++
 mex/segy_get_ntraces_mex.c          |   14 +
 mex/segy_get_segy_header_mex.c      |   44 ++
 mex/segy_get_trace_header_mex.c     |   42 ++
 mex/segy_get_traces_mex.c           |   76 ++
 mex/segy_interpret_segycube_mex.c   |    8 +
 mex/segy_put_headers_mex.c          |   62 ++
 mex/segy_put_traces_mex.c           |   69 ++
 mex/segy_read_write_line_mex.c      |  114 +++
 mex/segyspec_mex.c                  |  148 ++++
 mex/segyutil.c                      |   89 +++
 mex/segyutil.h                      |   26 +
 python/CMakeLists.txt               |    2 +
 python/cwrap/CMakeLists.txt         |   10 +
 python/cwrap/__init__.py            |    5 +
 python/cwrap/basecclass.py          |  100 +++
 python/cwrap/basecenum.py           |  119 ++++
 python/cwrap/basecvalue.py          |   47 ++
 python/cwrap/metacwrap.py           |   46 ++
 python/cwrap/prototype.py           |  147 ++++
 python/segyio/CMakeLists.txt        |   11 +
 python/segyio/__init__.py           |   42 ++
 python/segyio/binfield.py           |   70 ++
 python/segyio/create.py             |  135 ++++
 python/segyio/open.py               |   74 ++
 python/segyio/segy.py               | 1302 +++++++++++++++++++++++++++++++++++
 python/segyio/segysampleformat.py   |   24 +
 python/segyio/tracefield.py         |  189 +++++
 python/segyio/tracesortingformat.py |   13 +
 src/applications/segyinfo.c         |  141 ++++
 src/applications/segyinspect.c      |  167 +++++
 src/segyio/segy.c                   | 1131 ++++++++++++++++++++++++++++++
 src/segyio/segy.h                   |  393 +++++++++++
 src/spec/segyspec.c                 |  129 ++++
 src/spec/segyspec.h                 |   33 +
 tests/CMakeLists.txt                |   14 +
 tests/test-data/small.sgy           |  Bin 0 -> 14600 bytes
 tests/test-data/text.sgy            |  Bin 0 -> 3600 bytes
 tests/test_segy.c                   |  609 ++++++++++++++++
 tests/test_segy.py                  |  469 +++++++++++++
 tests/test_segy_mex.m               |  230 +++++++
 tests/test_segyspec.c               |   79 +++
 tests/test_segyspec_mex.m           |   88 +++
 tests/test_utils.c                  |  128 ++++
 tests/unittest.h                    |   43 ++
 67 files changed, 8553 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..ab2bb99
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,12 @@
+before_script:
+    - source /project/res/SDP_bashrc
+    - mkdir build
+    - cd build
+
+build:
+  script:
+    - cmake -D CMAKE_C_COMPILER=/opt/rh/devtoolset-3/root/usr/bin/gcc ..
+    - make -j4
+    - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD
+    - ctest -j4
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..cc1a986
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,54 @@
+cmake_minimum_required(VERSION 2.8)
+project(segyio)
+
+option(BUILD_MEX            "Build matlab mex files" OFF)
+
+include(cmake/python.cmake)
+enable_testing()
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
+
+if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+    set (CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Default install path" FORCE )
+endif()
+
+#set(CMAKE_BUILD_TYPE RELEASE)
+
+include_directories(src)
+
+set(SOURCE_FILES src/segyio/segy.c src/spec/segyspec.c)
+
+install(FILES src/segyio/segy.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/segyio)
+
+add_library(segyio-static STATIC ${SOURCE_FILES})
+set_target_properties(segyio-static PROPERTIES OUTPUT_NAME segyio CLEAN_DIRECT_OUTPUT 1)
+set_target_properties(segyio-static PROPERTIES COMPILE_FLAGS "-fPIC")
+
+add_library(segyio-shared SHARED ${SOURCE_FILES})
+set_target_properties(segyio-shared PROPERTIES OUTPUT_NAME segyio CLEAN_DIRECT_OUTPUT 1)
+
+install(TARGETS segyio-static segyio-shared DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
+
+add_executable(segyinfo src/applications/segyinfo.c)
+target_link_libraries(segyinfo segyio-static)
+add_dependencies(segyinfo segyio-static)
+
+add_executable(segyinspect src/applications/segyinspect.c)
+target_link_libraries(segyinspect segyio-static)
+add_dependencies(segyinspect segyio-static)
+
+install(TARGETS segyinfo DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
+install(TARGETS segyinspect DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
+
+if (BUILD_MEX)
+    add_subdirectory(mex)
+else (BUILD_MEX)
+    unset(MATLAB_MCC CACHE)
+    unset(MATLAB_MEX CACHE)
+    unset(MATLAB_MEXEXT CACHE)
+    unset(MATLAB_ROOT CACHE)
+endif()
+
+add_subdirectory(python)
+add_subdirectory(tests)
+add_subdirectory(examples)
diff --git a/License.md b/License.md
new file mode 100644
index 0000000..4900f40
--- /dev/null
+++ b/License.md
@@ -0,0 +1,157 @@
+### GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 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.
+
+This version of the GNU Lesser General Public License incorporates the
+terms and conditions of version 3 of the GNU General Public License,
+supplemented by the additional permissions listed below.
+
+#### 0. Additional Definitions.
+
+As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the
+GNU General Public License.
+
+"The Library" refers to a covered work governed by this License, other
+than an Application or a Combined Work as defined below.
+
+An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+#### 1. Exception to Section 3 of the GNU GPL.
+
+You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+#### 2. Conveying Modified Versions.
+
+If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+-   a) under this License, provided that you make a good faith effort
+    to ensure that, in the event an Application does not supply the
+    function or data, the facility still operates, and performs
+    whatever part of its purpose remains meaningful, or
+-   b) under the GNU GPL, with none of the additional permissions of
+    this License applicable to that copy.
+
+#### 3. Object Code Incorporating Material from Library Header Files.
+
+The object code form of an Application may incorporate material from a
+header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+-   a) Give prominent notice with each copy of the object code that
+    the Library is used in it and that the Library and its use are
+    covered by this License.
+-   b) Accompany the object code with a copy of the GNU GPL and this
+    license document.
+
+#### 4. Combined Works.
+
+You may convey a Combined Work under terms of your choice that, taken
+together, effectively do not restrict modification of the portions of
+the Library contained in the Combined Work and reverse engineering for
+debugging such modifications, if you also do each of the following:
+
+-   a) Give prominent notice with each copy of the Combined Work that
+    the Library is used in it and that the Library and its use are
+    covered by this License.
+-   b) Accompany the Combined Work with a copy of the GNU GPL and this
+    license document.
+-   c) For a Combined Work that displays copyright notices during
+    execution, include the copyright notice for the Library among
+    these notices, as well as a reference directing the user to the
+    copies of the GNU GPL and this license document.
+-   d) Do one of the following:
+    -   0) Convey the Minimal Corresponding Source under the terms of
+        this License, and the Corresponding Application Code in a form
+        suitable for, and under terms that permit, the user to
+        recombine or relink the Application with a modified version of
+        the Linked Version to produce a modified Combined Work, in the
+        manner specified by section 6 of the GNU GPL for conveying
+        Corresponding Source.
+    -   1) Use a suitable shared library mechanism for linking with
+        the Library. A suitable mechanism is one that (a) uses at run
+        time a copy of the Library already present on the user's
+        computer system, and (b) will operate properly with a modified
+        version of the Library that is interface-compatible with the
+        Linked Version.
+-   e) Provide Installation Information, but only if you would
+    otherwise be required to provide such information under section 6
+    of the GNU GPL, and only to the extent that such information is
+    necessary to install and execute a modified version of the
+    Combined Work produced by recombining or relinking the Application
+    with a modified version of the Linked Version. (If you use option
+    4d0, the Installation Information must accompany the Minimal
+    Corresponding Source and Corresponding Application Code. If you
+    use option 4d1, you must provide the Installation Information in
+    the manner specified by section 6 of the GNU GPL for conveying
+    Corresponding Source.)
+
+#### 5. Combined Libraries.
+
+You may place library facilities that are a work based on the Library
+side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+-   a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities, conveyed under the terms of this License.
+-   b) Give prominent notice with the combined library that part of it
+    is a work based on the Library, and explaining where to find the
+    accompanying uncombined form of the same work.
+
+#### 6. Revised Versions of the GNU Lesser General Public License.
+
+The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser 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 Library
+as you received it specifies that a certain numbered version of the
+GNU Lesser General Public License "or any later version" applies to
+it, you have the option of following the terms and conditions either
+of that published version or of any later version published by the
+Free Software Foundation. If the Library as you received it does not
+specify a version number of the GNU Lesser General Public License, you
+may choose any version of the GNU Lesser General Public License ever
+published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6498f12
--- /dev/null
+++ b/README.md
@@ -0,0 +1,113 @@
+# SEGY IO #
+
+## Introduction ##
+
+Segyio is a small LGPL licensed C library for easy interaction with SEG Y
+formatted seismic data, with language bindings for Python and Matlab. Segyio is
+an attempt to create an easy-to-use, embeddable, community-oriented library for
+seismic applications. Features are added as they are needed; suggestions and
+contributions of all kinds are very welcome.
+
+## Feature summary ##
+ * A low-level C interface with few assumptions; easy to bind to other
+   languages.
+ * Read and write binary and textual headers.
+ * Read and write traces, trace headers.
+ * Easy to use and native-feeling python interface with numpy integration.
+
+## Getting started ##
+
+When Segyio is built and installed, you're ready to start programming! For
+examples and documentation, check out the examples in the examples directory.
+If you're using python, pydoc is used, so fire up your favourite python
+interpreter and type `help(segyio)` to get started.
+
+### Requirements ###
+
+To build and use Segyio you need:
+ * A C99 compatible C compiler (tested mostly on gcc and clang)
+ * [CMake](https://cmake.org/) version 2.8 or greater
+ * [Python](https://www.python.org/) 2.7. We're working on 3.x support, help
+   appreciated!
+
+### Building ###
+
+#### Users ####
+
+To build and install Segyio, perform the following actions in your console:
+
+```
+git clone https://github.com/Statoil/segyio
+cd segyio
+mkdir build
+cmake .. -DCMAKE_BUILD_TYPE=Release
+make
+make install
+```
+
+Make install must be done as root for a system install; if you want to install
+in your home directory, add `-DCMAKE_INSTALL_PREFIX=~/` or some other
+approperiate directory. Remember to update your $PATH!
+
+##### Matlab support #####
+
+To build the matlab bindings, invoke CMake with the option `-DBUILD_MEX=ON`. In
+some environments the Matlab binaries are in a non-standard location, in which
+case you need to help CMake find the matlab binaries by passing
+`-DMATLAB_ROOT=/path/to/matlab`.
+
+#### Developers ####
+
+It's recommended to build in debug mode to get more warnings and to embed debug
+symbols in the objects. Substituting `Debug` for `Release` in the
+`CMAKE_BUILD_TYPE` is plenty.
+
+Tests are located in the tests directory, and it's highly recommended that new
+features added are demonstrated for correctness and contract by adding a test.
+Feel free to use the tests already written as a guide.
+
+After building Segyio you can run the tests with `ctest`, executed from the
+build directory.
+
+Please note that to run the python tests you need to let your environment know
+where to find the segyio library files. On linux (bash) this is accomplished by being
+in the build directory and executing: `LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH`
+or by passing ctest a modified environment
+`LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH ctest`.
+
+## Contributing ##
+
+We welcome all kinds of contributions, including code, bug reports, issues,
+feature requests and documentation. The preferred way of submitting a
+contribution is to either make an
+[issue](https://github.com/Statoil/SegyIO/issues) on github or by forking the
+project on github and making a pull request.
+
+The current version supports:
+
+* Reading EBCDIC Textual Header
+* Reading Binary Header and selected fields.
+* Read individual traces
+* Inspect a SEGY file and identify crossline, inline and sample indexes
+
+
+## Reproducing the test data ##
+
+Small SEG Y formatted files are included in the repository for test purposes.
+Phyiscally speaking the data is non-sensical, but it is reproducible by using
+Segyio. The tests file are located in the tests/test-data directory. To
+reproduce the data file, build Segyio and run the test program `make-file.py`
+as such:
+
+```
+python examples/make-file.py out.sgy 50 1 6 20 25
+```
+
+If you have have small data files with a free license, feel free to submit it
+to the project!
+
+## History ##
+Segyio was initially written and is maintained by [Statoil
+ASA](http://www.statoil.com/) as a free, simple, easy-to-use way of interacting
+with seismic data that can be tailored to our needs, and as contribution to the
+free software community.
diff --git a/cmake/matlab.cmake b/cmake/matlab.cmake
new file mode 100644
index 0000000..d4e3a93
--- /dev/null
+++ b/cmake/matlab.cmake
@@ -0,0 +1,257 @@
+# This module looks for mex, the MATLAB compiler.
+# The following variables are defined when the script completes:
+#   MATLAB_MEX: location of mex compiler
+#   MATLAB_ROOT: root of MATLAB installation
+#   MATLABMEX_FOUND: 0 if not found, 1 if found
+
+SET(MATLABMEX_FOUND 0)
+SET(MATLABMCC_FOUND 0)
+
+IF(WIN32)
+  # Win32 is Untested
+  # Taken from the older FindMatlab.cmake script as well as
+  # the modifications by Ramon Casero and Tom Doel for Gerardus.
+
+  # Search for a version of Matlab available, starting from the most modern one
+  # to older versions.
+  FOREACH(MATVER "7.20" "7.19" "7.18" "7.17" "7.16" "7.15" "7.14" "7.13" "7.12" "7.11" "7.10" "7.9" "7.8" "7.7" "7.6" "7.5" "7.4")
+    IF((NOT DEFINED MATLAB_ROOT)
+        OR ("${MATLAB_ROOT}" STREQUAL "")
+        OR ("${MATLAB_ROOT}" STREQUAL "/registry"))
+      GET_FILENAME_COMPONENT(MATLAB_ROOT
+        "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MathWorks\\MATLAB\\${MATVER};MATLABROOT]"
+        ABSOLUTE)
+      SET(MATLAB_VERSION ${MATVER})
+    ENDIF((NOT DEFINED MATLAB_ROOT)
+      OR ("${MATLAB_ROOT}" STREQUAL "")
+      OR ("${MATLAB_ROOT}" STREQUAL "/registry"))
+  ENDFOREACH(MATVER)
+
+  FIND_PROGRAM(MATLAB_MEX
+          mex
+          ${MATLAB_ROOT}/bin
+          )
+  FIND_PROGRAM(MATLAB_MCC
+          mex
+          ${MATLAB_ROOT}/bin
+          )
+ELSE(WIN32)
+  # Check if this is a Mac.
+  IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+    # Mac is untested
+    # Taken from the older FindMatlab.cmake script as
+    # well as the modifications by Ramon Casero and Tom Doel for Gerardus.
+
+   SET(LIBRARY_EXTENSION .dylib)
+
+    # If this is a Mac and the attempts to find MATLAB_ROOT have so far failed,~
+    # we look in the applications folder
+    IF((NOT DEFINED MATLAB_ROOT) OR ("${MATLAB_ROOT}" STREQUAL ""))
+
+    # Search for a version of Matlab available, starting from the most modern
+    # one to older versions
+      FOREACH(MATVER "R2013b" "R2013a" "R2012b" "R2012a" "R2011b" "R2011a" "R2010b" "R2010a" "R2009b" "R2009a" "R2008b")
+        IF((NOT DEFINED MATLAB_ROOT) OR ("${MATLAB_ROOT}" STREQUAL ""))
+          IF(EXISTS /Applications/MATLAB_${MATVER}.app)
+            SET(MATLAB_ROOT /Applications/MATLAB_${MATVER}.app)
+
+          ENDIF(EXISTS /Applications/MATLAB_${MATVER}.app)
+        ENDIF((NOT DEFINED MATLAB_ROOT) OR ("${MATLAB_ROOT}" STREQUAL ""))
+      ENDFOREACH(MATVER)
+
+    ENDIF((NOT DEFINED MATLAB_ROOT) OR ("${MATLAB_ROOT}" STREQUAL ""))
+
+    FIND_PROGRAM(MATLAB_MEX
+            mex
+            PATHS
+            ${MATLAB_ROOT}/bin
+            )
+    FIND_PROGRAM(MATLAB_MCC
+            mcc
+            PATHS
+            ${MATLAB_ROOT}/bin
+            )
+
+  ELSE(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+    # On a Linux system.  The goal is to find MATLAB_ROOT.
+    SET(LIBRARY_EXTENSION .so)
+
+    FIND_PROGRAM(MATLAB_MEX
+            mex
+            PATHS
+            /prog/matlab/R2014B/bin # Statoil location
+            ${MATLAB_ROOT}/bin
+            /opt/matlab/bin
+            /usr/local/matlab/bin
+            $ENV{HOME}/matlab/bin
+            # Now all the versions
+            /opt/matlab/[rR]20[0-9][0-9][abAB]/bin
+            /usr/local/matlab/[rR]20[0-9][0-9][abAB]/bin
+            /opt/matlab-[rR]20[0-9][0-9][abAB]/bin
+            /opt/matlab_[rR]20[0-9][0-9][abAB]/bin
+            /usr/local/matlab-[rR]20[0-9][0-9][abAB]/bin
+            /usr/local/matlab_[rR]20[0-9][0-9][abAB]/bin
+            $ENV{HOME}/matlab/[rR]20[0-9][0-9][abAB]/bin
+            $ENV{HOME}/matlab-[rR]20[0-9][0-9][abAB]/bin
+            $ENV{HOME}/matlab_[rR]20[0-9][0-9][abAB]/bin
+            )
+
+    GET_FILENAME_COMPONENT(MATLAB_MEX "${MATLAB_MEX}" REALPATH)
+    GET_FILENAME_COMPONENT(MATLAB_BIN_ROOT "${MATLAB_MEX}" PATH)
+    # Strip ./bin/.
+    GET_FILENAME_COMPONENT(MATLAB_ROOT "${MATLAB_BIN_ROOT}" PATH)
+
+    FIND_PROGRAM(MATLAB_MCC
+            mcc
+            PATHS
+            ${MATLAB_ROOT}/bin
+            )
+
+    FIND_PROGRAM(MATLAB_MEXEXT
+            mexext
+            PATHS
+            ${MATLAB_ROOT}/bin
+            )
+
+    GET_FILENAME_COMPONENT(MATLAB_MCC "${MATLAB_MCC}" REALPATH)
+    GET_FILENAME_COMPONENT(MATLAB_MEXEXT "${MATLAB_MEXEXT}" REALPATH)
+  ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ENDIF(WIN32)
+
+IF(NOT EXISTS "${MATLAB_MEX}" AND "${MatlabMex_FIND_REQUIRED}")
+  MESSAGE(FATAL_ERROR "Could not find MATLAB mex compiler; try specifying MATLAB_ROOT.")
+ELSE(NOT EXISTS "${MATLAB_MEX}" AND "${MatlabMex_FIND_REQUIRED}")
+  IF(EXISTS "${MATLAB_MEX}")
+    MESSAGE(STATUS "Found MATLAB mex compiler: ${MATLAB_MEX}")
+    MESSAGE(STATUS "MATLAB root: ${MATLAB_ROOT}")
+    SET(MATLABMEX_FOUND 1)
+  ENDIF(EXISTS "${MATLAB_MEX}")
+ENDIF(NOT EXISTS "${MATLAB_MEX}" AND "${MatlabMex_FIND_REQUIRED}")
+
+IF(NOT EXISTS "${MATLAB_MCC}" AND "${MatlabMcc_FIND_REQUIRED}")
+  MESSAGE(FATAL_ERROR "Could not find MATLAB mcc compiler; try specifying MATLAB_ROOT.")
+ELSE(NOT EXISTS "${MATLAB_MCC}" AND "${MatlabMcc_FIND_REQUIRED}")
+  IF(EXISTS "${MATLAB_MCC}")
+    MESSAGE(STATUS "Found MATLAB mcc compiler: ${MATLAB_MCC}")
+    SET(MATLABMCC_FOUND 1)
+  ENDIF(EXISTS "${MATLAB_MCC}")
+ENDIF(NOT EXISTS "${MATLAB_MCC}" AND "${MatlabMcc_FIND_REQUIRED}")
+
+MARK_AS_ADVANCED(
+  MATLABMEX_FOUND
+)
+
+SET(MATLAB_ROOT ${MATLAB_ROOT} CACHE FILEPATH "Path to a matlab installation")
+
+EXECUTE_PROCESS(COMMAND ${MATLAB_MEXEXT} OUTPUT_VARIABLE MEXEXT OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+macro(mexo MEX_OBJECT)
+    set(MEX_CFLAGS -fPIC  -std=c99 -Werror)
+    set(MEX_LDFLAGS)
+
+    get_property(dirs DIRECTORY . PROPERTY INCLUDE_DIRECTORIES)
+    foreach(dir ${dirs})
+        set(MEX_CFLAGS ${MEX_CFLAGS} -I${dir})
+    endforeach()
+
+    set(MEX_LDFLAGS -shared)
+
+    set(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${MEX_OBJECT}.c)
+    set(HEADER ${CMAKE_CURRENT_SOURCE_DIR}/${MEX_OBJECT}.h)
+    set(OBJECT ${CMAKE_CURRENT_BINARY_DIR}/${MEX_OBJECT}.o)
+
+    add_custom_command(OUTPUT ${OBJECT}
+            COMMAND
+            ${MATLAB_MEX}
+            -c
+            CC="${CMAKE_C_COMPILER}"
+            LD="${CMAKE_CXX_COMPILER}"
+            CFLAGS="${MEX_CFLAGS}"
+            LDFLAGS="${MEX_LDFLAGS}"
+            -outdir ${CMAKE_CURRENT_BINARY_DIR}
+            ${SOURCE}
+            DEPENDS
+            ${SOURCE}
+            ${HEADER}
+            )
+
+    add_custom_target(${MEX_OBJECT} ALL DEPENDS ${OBJECT})
+endmacro()
+
+macro(mex MEX_NAME )
+    set(DEP ${ARG2})
+    set(MEX_CFLAGS -fPIC  -std=c99 -Werror)
+    set(MEX_LDFLAGS)
+
+    get_property(dirs DIRECTORY . PROPERTY INCLUDE_DIRECTORIES)
+    foreach(dir ${dirs})
+        set(MEX_CFLAGS ${MEX_CFLAGS} -I${dir})
+    endforeach()
+
+    set(MEX_LDFLAGS -shared)
+
+    set(MEX_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${MEX_NAME}.c)
+    set(MEX_RESULT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${MEX_NAME}.${MEXEXT})
+    add_custom_command(OUTPUT ${MEX_RESULT_FILE}
+            COMMAND
+            ${MATLAB_MEX}
+            CC="${CMAKE_C_COMPILER}"
+            CXX="${CMAKE_CXX_COMPILER}"
+            LD="${CMAKE_CXX_COMPILER}"
+            CFLAGS="${MEX_CFLAGS}"
+            LDFLAGS="${MEX_LDFLAGS}"
+            ${OBJECT}
+            $<TARGET_FILE:segyio-static>
+            -outdir ${CMAKE_CURRENT_BINARY_DIR}
+            ${MEX_SOURCE_FILE}
+            DEPENDS
+            ${MEX_SOURCE_FILE}
+            segyio-static
+            segyutil.o
+            )
+
+    add_custom_target(${MEX_NAME} ALL DEPENDS ${MEX_RESULT_FILE} segyutil.o)
+
+    set(${MEX_NAME}_TARGET ${MEX_NAME} PARENT_SCOPE)
+    set(${MEX_NAME}_FILE ${MEX_RESULT_FILE} PARENT_SCOPE)
+
+endmacro()
+
+
+# this isn't meant to be run directly; use matlab_add_test or
+# matlab_add_example instead
+function(matlab_test TYPE TESTNAME MCC_SOURCE_FILE MCC_TARGET_NAME)
+    set(RESULT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${MCC_TARGET_NAME})
+
+    add_custom_command(OUTPUT ${RESULT_FILE}
+            COMMAND
+                ${MATLAB_MCC}
+                -I ${CMAKE_BINARY_DIR}/mex
+                -m ${MCC_TARGET_NAME}
+                -d ${CMAKE_CURRENT_BINARY_DIR}
+            DEPENDS
+                ${CMAKE_CURRENT_LIST_DIR}/${MCC_SOURCE_FILE}
+                ${segyspec_mex_FILE}
+                ${segyheaders_mex_FILE}
+                ${CMAKE_SOURCE_DIR}/mex/SegySpec.m
+            WORKING_DIRECTORY
+                ${CMAKE_CURRENT_SOURCE_DIR}
+            )
+
+    add_custom_target(${MCC_TARGET_NAME} ALL DEPENDS ${RESULT_FILE} ${segyspec_mex_TARGET} ${segyheaders_mex_TARGET})
+
+    add_test(NAME ${TESTNAME}
+            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${TYPE}
+            COMMAND run_${MCC_TARGET_NAME}.sh ${MATLAB_ROOT} ${ARGN}
+            )
+endfunction()
+
+function(add_matlab_test TESTNAME MCC_SOURCE_FILE MCC_TARGET_NAME)
+    matlab_test(tests ${TESTNAME} ${MCC_SOURCE_FILE} ${MCC_TARGET_NAME})
+endfunction()
+
+# add_matlab_example takes an arbitrary number of arguments which it will
+# forward to the example program
+function(add_matlab_example TESTNAME MCC_SOURCE_FILE MCC_TARGET_NAME)
+    matlab_test(examples ${TESTNAME} ${MCC_SOURCE_FILE} ${MCC_TARGET_NAME} ${ARGN})
+endfunction()
diff --git a/cmake/python.cmake b/cmake/python.cmake
new file mode 100644
index 0000000..56fead3
--- /dev/null
+++ b/cmake/python.cmake
@@ -0,0 +1,57 @@
+configure_file(cmake/test_runner.py tests/test_runner.py COPYONLY)
+
+if("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*")
+    set(SEP "\\;")
+else() # e.g. Linux
+    set(SEP ":")
+endif()
+
+function(add_memcheck_test NAME BINARY)
+    set(memcheck_command "valgrind --trace-children=yes --leak-check=full --error-exitcode=31415")
+    separate_arguments(memcheck_command)
+    add_test(memcheck_${NAME} ${memcheck_command} ./${BINARY})
+endfunction(add_memcheck_test)
+
+function(add_python_package PACKAGE_NAME PACKAGE_PATH PYTHON_FILES)
+    add_custom_target(package_${PACKAGE_NAME} ALL)
+
+    foreach (file ${PYTHON_FILES})
+        add_custom_command(TARGET package_${PACKAGE_NAME}
+                COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/python/${PACKAGE_PATH}
+                COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${file} ${CMAKE_BINARY_DIR}/python/${PACKAGE_PATH}
+                )
+    endforeach ()
+    install(FILES ${PYTHON_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python2.7/site-packages/${PACKAGE_PATH})
+endfunction()
+
+function(add_python_test TESTNAME PYTHON_TEST_FILE)
+    configure_file(${PYTHON_TEST_FILE} ${PYTHON_TEST_FILE} COPYONLY)
+        
+    add_test(NAME ${TESTNAME}
+            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/tests
+            COMMAND python test_runner.py ${PYTHON_TEST_FILE}
+            )
+    set_tests_properties(${TESTNAME} PROPERTIES ENVIRONMENT
+                        "PYTHONPATH=${CMAKE_BINARY_DIR}/python${SEP}$ENV{PYTHONPATH}"
+                        )
+endfunction()
+
+function(add_python_example TESTNAME PYTHON_TEST_FILE)
+    configure_file(${PYTHON_TEST_FILE} ${PYTHON_TEST_FILE} COPYONLY)
+
+    add_test(NAME ${TESTNAME}
+            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/examples
+            COMMAND python ${PYTHON_TEST_FILE} ${ARGN}
+            )
+    set_tests_properties(${TESTNAME} PROPERTIES ENVIRONMENT
+                        "PYTHONPATH=${CMAKE_BINARY_DIR}/python${SEP}$ENV{PYTHONPATH}"
+                        )
+endfunction()
+
+function(add_segyio_test TESTNAME TEST_SOURCES)
+    add_executable(test_${TESTNAME} unittest.h "${TEST_SOURCES}")
+    target_link_libraries(test_${TESTNAME} segyio-static m)
+    add_dependencies(test_${TESTNAME} segyio-static)
+    add_test(NAME ${TESTNAME} COMMAND ${EXECUTABLE_OUTPUT_PATH}/test_${TESTNAME})
+    add_memcheck_test(${TESTNAME} ${EXECUTABLE_OUTPUT_PATH}/test_${TESTNAME})
+endfunction()
diff --git a/cmake/test_runner.py b/cmake/test_runner.py
new file mode 100755
index 0000000..31e6fb9
--- /dev/null
+++ b/cmake/test_runner.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+import inspect
+import os
+import sys
+
+import imp
+
+try:
+    from unittest2 import TextTestRunner, TestLoader, TestCase
+except ImportError:
+    from unittest import TextTestRunner, TestLoader, TestCase
+
+
+def runTestCase(tests, verbosity=0):
+    test_result = TextTestRunner(verbosity=verbosity).run(tests)
+
+    if len(test_result.errors) or len(test_result.failures):
+        test_result.printErrors()
+        return False
+    else:
+        return True
+
+def getTestClassFromModule(module_path):
+    test_module = imp.load_source('test', module_path)
+    for name, obj in inspect.getmembers(test_module):
+        if inspect.isclass(obj) and issubclass(obj, TestCase) and not obj == TestCase:
+            return obj
+
+def getTestsFromModule(module_path):
+    klass = getTestClassFromModule(module_path)
+    if klass is None:
+        raise UserWarning("No tests classes found in: '%s'" % module_path)
+    
+    loader = TestLoader()
+    return loader.loadTestsFromTestCase(klass)
+            
+
+if __name__ == '__main__':
+    test_module = sys.argv[1]
+    argv = []
+
+    tests = getTestsFromModule(test_module)
+
+    # Set verbosity to 2 to see which test method in a class that fails.
+    if runTestCase(tests, verbosity=0):
+        sys.exit(0)
+    else:
+        sys.exit(1)
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..46036bb
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,6 @@
+configure_file(../tests/test-data/small.sgy test-data/small.sgy 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/about.py b/examples/about.py
new file mode 100644
index 0000000..91a7649
--- /dev/null
+++ b/examples/about.py
@@ -0,0 +1,49 @@
+import sys
+from segyio import TraceField
+import segyio
+
+def list_byte_offset_names():
+    print("Available offsets and their corresponding byte value:")
+    for x in TraceField.enums():
+        print("  %s: %d" % (str(x), x))
+
+if __name__ == '__main__':
+    if len( sys.argv ) < 4:
+        list_byte_offset_names()
+        sys.exit( "Usage: about.py [file] [inline] [crossline]" )
+
+    # we need a way to convert from run-time inline/crossline argument (as
+    # text) to the internally used TraceField enum. Make a string -> TraceField
+    # map and look up into that. this dictionary comprehension creates that
+    fieldmap = { str( x ).lower(): x for x in TraceField.enums() }
+
+    filename = sys.argv[ 1 ]
+    inline_name, crossline_name = sys.argv[ 2 ].lower(), sys.argv[ 3 ].lower()
+
+    # exit if inline or crossline are unknown
+    if inline_name not in fieldmap:
+        list_byte_offset_names()
+        sys.exit( "Unknown inline field '%s'" % sys.argv[ 2 ] )
+
+    if crossline_name not in fieldmap:
+        list_byte_offset_names()
+        sys.exit( "Unknown crossline field '%s'" % sys.argv[ 3 ] )
+
+    inline, crossline = fieldmap[ inline_name ], fieldmap[ crossline_name ]
+
+    with segyio.open(filename, "r", inline, crossline) as f:
+        print("About '%s':" % filename)
+        print("Format type: %s" % f.format)
+        print("Offset count: %d" % f.offsets)
+        print("ilines: %s" % ", ".join(map(str, f.ilines)))
+        print("xlines: %s" % ", ".join(map(str, f.xlines)))
+
+    print "+------+"
+
+    with segyio.open(filename, "r", crossline, inline) as f:
+        # with swapped inline/crossline
+        print("About '%s':" % filename)
+        print("Format type: %s" % f.format)
+        print("Offset count: %d" % f.offsets)
+        print("ilines: %s" % ", ".join(map(str, f.ilines)))
+        print("xlines: %s" % ", ".join(map(str, f.xlines)))
diff --git a/examples/copy-sub-cube.py b/examples/copy-sub-cube.py
new file mode 100644
index 0000000..900680e
--- /dev/null
+++ b/examples/copy-sub-cube.py
@@ -0,0 +1,41 @@
+import sys
+import segyio
+
+# this program creates a new subcube, taking the first 5 lines in both
+# directions, and reduces the trace size to 20 samples
+def main():
+    if len(sys.argv) < 3:
+        sys.exit("Usage: {} [source-file] [destination-file]".format(sys.argv[0]))
+
+    sourcefile = sys.argv[1]
+    destfile = sys.argv[2]
+
+    with segyio.open(sourcefile) as src:
+        spec = segyio.spec()
+        spec.sorting = int(src.sorting)
+        spec.format  = int(src.format)
+        spec.samples = 50
+        spec.ilines = src.ilines[:5]
+        spec.xlines = src.xlines[:5]
+
+        with segyio.create(destfile, spec) as dst:
+            # Copy all textual headers, including possible extended
+            for i in range(1 + src.ext_headers):
+                dst.text[i] = src.text[i]
+
+            # copy the binary header, then insert the modifications needed for
+            # the new shape
+            dst.bin = src.bin
+            dst.bin = { segyio.BinField.Samples: 50,
+                        segyio.BinField.Traces: 5 * 5
+                      }
+
+            # Copy all headers in the new inlines. Since we know we're copying
+            # the five first we don't have to take special care to update
+            # headers
+            dst.header.iline = src.header.iline
+            # the copy traces (in line mode)
+            dst.iline = src.iline
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/make-file.py b/examples/make-file.py
new file mode 100644
index 0000000..8179b18
--- /dev/null
+++ b/examples/make-file.py
@@ -0,0 +1,55 @@
+import sys
+import numpy as np
+import segyio
+from itertools import izip as izip
+
+def main():
+    if len(sys.argv) < 7:
+        sys.exit("Usage: {} [file] [samples] [first iline] [last iline] [first xline] [last xline]".format(sys.argv[0]))
+
+    spec = segyio.spec()
+    filename = sys.argv[1]
+
+# 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 defautls. This is
+# the absolute minimal specification for a N-by-M volume
+    spec.sorting = 2
+    spec.format = 1
+    spec.samples = int(sys.argv[2])
+    spec.ilines = range(*map(int, sys.argv[3:5]))
+    spec.xlines = range(*map(int, sys.argv[5:7]))
+
+    with segyio.create(filename, spec) as f:
+        # one inline consists of 50 traces
+        # which in turn consists of 2000 samples
+        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 }
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/segyview.py b/examples/segyview.py
new file mode 100644
index 0000000..b3b6c01
--- /dev/null
+++ b/examples/segyview.py
@@ -0,0 +1,32 @@
+import sys
+import segyio
+import numpy as np
+import matplotlib.pyplot as plt
+from pylab import *
+import time
+ 
+def main():
+    if len( sys.argv ) < 2:
+        sys.exit("Usage: segyview.py [file]")
+
+
+    filename = sys.argv[1]
+    data = None
+    ion()
+
+    with segyio.open( filename, "r" ) as f:
+         data = f.xline[1428]
+         #plt.colorbar()
+         imgplot = plt.imshow(data, vmin=1500, vmax=1600)
+         plt.show(False)
+         for i in range(1429,1440):
+              data = f.xline[i]
+              imgplot.set_data(data)
+              plt.draw()
+              plt.pause(0.1) #Note this correction
+
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/write.py b/examples/write.py
new file mode 100644
index 0000000..68ebb47
--- /dev/null
+++ b/examples/write.py
@@ -0,0 +1,84 @@
+import sys
+import segyio
+import numpy as np
+
+def main():
+    if len( sys.argv ) < 2:
+        sys.exit("Usage: write.py [file]")
+
+    filename = sys.argv[1]
+
+    # the mode parameter is passed directly to C's fopen
+    # opening the file for writing requires r+, not rw because rw would
+    # truncate (effectively destroy) the file, and r+ would preserve the size
+    with segyio.open( filename, "r+" ) as src:
+
+        # read trace 0, then double all values
+        trace = src.trace[0]
+        trace *= 2
+
+        # write trace 0 back to disk
+        src.trace[0] = trace
+
+        # read trace 1, but re-use the memory for speed
+        trace = src.trace[1, trace]
+        # square all values. the trace is a plain numpy array
+        trace = np.square(trace, trace)
+        # write the trace back to disk, but at trace 2
+        src.trace[2] = trace
+
+        # read every other trace, from 10 through 20
+        # then write them to every third step from 40 through 52
+        # i.e. 40, 43, 46...
+        # slices yield a generator, so only one numpy array is created
+        for tr, i in zip(src.trace[10:20:2], range(2,13,3)):
+            src.trace[i] = tr
+
+        # iterate over all traces in a file. this is a generator with a shared
+        # buffer, so it's quite efficient
+        tracesum = 0
+        for tr in src.trace:
+            # accumulate the traces' 30th value
+            tracesum += tr[30]
+
+        print("Trace sum: %f" % tracesum)
+
+        # write the iline at 2 to the iline at 3
+        sum3 = np.sum(src.iline[3])
+        src.iline[2] = src.iline[3]
+        # flush to make sure our changes to the file are visible
+        src.flush()
+        sum2 = np.sum(src.iline[2])
+
+        print("Accumulates of inlines 2 and 3: %f -- %f" % (sum2, sum3))
+
+        # ilines too are plain numpy ndarrays, with trace-major addressing
+        # i.e. iline[2,40] would be yield trace#2's 40th value
+        iline = src.iline[2]
+        # since ilines are numpy arrays they also support numpy operations
+        iline = np.add(iline, src.iline[4])
+
+        # lines too have generator support, so we accumulate the 2nd trace's
+        # 22nd value.
+        linesum = 0
+        for line in src.iline:
+            linesum += line[2,22]
+
+        print("Inline sum: %f" % linesum)
+
+        # xline access is identical to iline access
+        linesum = 0
+        for line in src.xline:
+            linesum += line[2,22]
+
+        print("Crossline sum: %f" % linesum)
+
+        # accessing a non-existing inline will raise a KeyError
+        try:
+            src.iline[5000]
+            assert(False)
+        except KeyError as e:
+            print(str(e))
+
+if __name__ == '__main__':
+    main()
diff --git a/mex/CMakeLists.txt b/mex/CMakeLists.txt
new file mode 100644
index 0000000..6fecdff
--- /dev/null
+++ b/mex/CMakeLists.txt
@@ -0,0 +1,40 @@
+include(../cmake/matlab.cmake REQUIRED)
+
+configure_file(Segy.m Segy.m)
+configure_file(SegySpec.m SegySpec.m)
+configure_file(SegySampleFormat.m SegySampleFormat.m)
+configure_file(TraceSortingFormat.m TraceSortingFormat.m)
+configure_file(TraceField.m TraceField.m)
+
+
+mexo(segyutil)
+mex(segyspec_mex)
+mex(segy_read_write_line_mex segyutil)
+mex(segy_get_header_mex segyutil)
+mex(segy_get_traces_mex segyutil)
+mex(segy_put_traces_mex segyutil)
+mex(segy_get_ntraces_mex segyutil)
+mex(segy_get_segy_header_mex segyutil)
+mex(segy_get_bfield_mex segyutil)
+mex(segy_get_trace_header_mex segyutil)
+mex(segy_get_field_mex segyutil)
+mex(segy_put_headers_mex segyutil)
+
+install(FILES
+        ${CMAKE_CURRENT_BINARY_DIR}/segyspec_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_read_write_line_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_get_header_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_get_traces_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_put_traces_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_get_ntraces_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_get_segy_header_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_get_bfield_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_get_trace_header_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_get_field_mex.mexa64
+        ${CMAKE_CURRENT_BINARY_DIR}/segy_put_headers_mex.mexa64
+        SegySpec.m
+        Segy.m
+        SegySampleFormat.m
+        TraceSortingFormat.m
+        DESTINATION
+        ${CMAKE_INSTALL_PREFIX}/matlab)
diff --git a/mex/Segy.m b/mex/Segy.m
new file mode 100644
index 0000000..a23e871
--- /dev/null
+++ b/mex/Segy.m
@@ -0,0 +1,550 @@
+classdef Segy
+% add comment here
+    properties
+        spec
+    end
+
+    methods(Static)
+
+        function obj = readInLine(spec, index)
+            obj = segy_read_write_line_mex(spec, index, max(size(spec.crossline_indexes)), spec.inline_indexes, spec.il_stride);
+        end
+
+        function obj = readCrossLine(spec, index)
+            obj = segy_read_write_line_mex(spec, index, max(size(spec.inline_indexes)), spec.crossline_indexes, spec.xl_stride);
+        end
+
+        function obj = writeCrossLine(spec, data, index)
+            segy_read_write_line_mex(spec, index, max(size(spec.inline_indexes)), spec.crossline_indexes, spec.xl_stride, data);
+            obj = data;
+        end
+
+        function obj = writeInLine(spec, data, index)
+            segy_read_write_line_mex(spec, index, max(size(spec.crossline_indexes)), spec.inline_indexes, spec.il_stride, data);
+            obj = data;
+        end
+
+        function data = get_line(cube, dir, n)
+            if strcmpi(dir, 'iline')
+                data = segy_read_write_line_mex(cube, n, max(size(cube.crossline_indexes)), cube.inline_indexes, cube.il_stride);
+
+            elseif strcmpi(dir, 'xline')
+                data = segy_read_write_line_mex(cube, n, max(size(cube.inline_indexes)), cube.crossline_indexes, cube.xl_stride);
+
+            else
+                error('Only iline and xline are valid directions.');
+            end
+        end
+
+        function data = put_line(cube, data, dir, n)
+            if strcmpi(dir, 'iline')
+                segy_read_write_line_mex(cube, n, max(size(cube.crossline_indexes)), cube.inline_indexes, cube.il_stride, data);
+
+            elseif strcmpi(dir, 'xline')
+                segy_read_write_line_mex(cube, n, max(size(cube.inline_indexes)), cube.crossline_indexes, cube.xl_stride, data );
+
+            else
+                error('Only iline and xline are valid directions.');
+            end
+        end
+
+        % Goal:
+        %   Fast reading of trace header words in segy filenamele.
+        %
+        % Algorithm:
+        %   Use keyword as specified in available_headers. If byte location is
+        %   known byte2headerword converts to keyword.
+        %
+        % Inputs:
+        %   filename    Filename of segyfile
+        %   headword    Name of header word to read (example: 'cdp')
+        %   notype      Optional. If number format is different than set in segy
+        %               header this can be set by notype. Valid numbers are 1,2,3,5
+        %               and 8 as spesified by SEG-Y rev 1 standard.
+        %
+        % Output:
+        %   name                  meaning
+        %   tr_heads              Header values
+        function [tr_heads, notrace] = get_header(filename, headword, notype)
+            if exist(filename, 'file') ~= 2
+                error('File does not exist')
+            end
+            % notype isn't really use so we ignore it (for interface compatilibty)
+
+            if ischar(headword)
+                headword = TraceField.(headword);
+            end
+
+            headword = int32(headword);
+            [x, y] = segy_get_header_mex(filename, headword);
+            tr_heads = x;
+            notrace = y;
+        end
+
+        % [data,dt,notype] = get_traces(filename,n1,n2,notype)
+        %
+        % Goal:
+        %   Fast reading of traces in segy volume. Not for front-end use. Use
+        %   get_line / get_slice / get_subcube instead.
+        %
+        % Algorithm:
+        %
+        %
+        % Inputs:
+        %   filename    Filename of segyfile
+        %   n1          (Optional) First trace. If no first and last trace is
+        %               specified, all file is read.
+        %   n2          (Optional) Last trace.
+        %   notype      Optional. If number format is different than set in segy
+        %               header this can be set by notype. Valid numbers are 1,2,3,5
+        %               and 8 as spesified by SEG-Y rev 1 standard.
+        %
+        % Output:
+        %   data        Traces read from file
+        %   dt          Sample interval
+        %   notype      Number format
+        function [data, dt, notype] = get_traces( filename, n1, n2, notype )
+            if exist(filename, 'file') ~= 2
+                error('File does not exist')
+            end
+
+            if nargin < 2
+                n1 = 1;
+            end
+            if nargin < 3
+                n2 = 0;
+            end
+
+            if nargin < 4
+                notype = -1;
+            end
+
+            [data, dt, notype] = segy_get_traces_mex( filename, n1 - 1, n2 - 1, notype );
+        end
+
+        %function ntraces = get_ntraces(filename);
+        %
+        % Goal
+        %   Count the number of traces in a segy file
+        %
+        % Inputs:
+        %   filename    Filename of segyfile
+        %
+        % Output:
+        %   notrace     number of traces
+        %               return 0 in case of error
+        function notrace = get_ntraces( filename )
+            if exist(filename, 'file') ~= 2
+                error('File does not exist')
+            end
+            notrace = segy_get_ntraces_mex( filename );
+        end
+
+        % Goal:
+        %   Interpret segy cube as a 3D cube and save information needed to access
+        %   the segy file as a cube in terms of inline and crossline numbers.
+        %
+        % inputs:
+        %   filename    filename of segy file
+        %   il_word     bytenumber or header word for inline number
+        %   xl_word     bytenumber or header word for crossline number
+        %   t0          Time (ms) / depth (m) of first sample. Optional (default = 0)
+        %
+        % output:
+        %   segycube    struct needed to access segy file as a cube.
+
+        function segycube = interpret_segycube(filename, il_word, xl_word, t0)
+            if exist(filename, 'file') ~= 2
+                error('File does not exist')
+            end
+
+            if nargin < 4
+                t0      = 0;
+            end
+            if nargin < 3
+                xl_word = TraceField.CROSSLINE_3D;
+            end
+            if nargin < 2
+                il_word = TraceField.INLINE_3D;
+            end
+
+            % for compatibility with old code; if argument is passed as a
+            % string, first convert to an enum, then pass that enum to the
+            % constructor
+            if ischar(il_word)
+                il_word = TraceField.(il_word);
+            end
+
+            if ischar(xl_word)
+                xl_word = TraceField.(xl_word);
+            end
+
+            segycube = SegySpec(filename, il_word, xl_word, t0);
+        end
+
+        %
+        % Goal:
+        %   Read a full cube from segycube struct.
+        %
+        % Inputs:
+        %   sc          Data as an interpreted segy cube from
+        %               'interpret_segycube.m'
+        % Output:
+        %   data        Extracted cube with same sorting as in segy.
+        %
+        function data = get_cube(sc)
+            data = Segy.get_traces(sc.filename);
+
+            if sc.trace_sorting_format == TraceSortingFormat.INLINE
+                data = reshape( data, size( sc.sample_indexes, 1 ), size( sc.xline, 1 ), size( sc.iline, 1 ) );
+            elseif sc.trace_sorting_format == TraceSortingFormat.XLINE
+                data = reshape( data, size( sc.sample_indexes, 1 ), size( sc.iline, 1 ), size( sc.xline, 1 ) );
+            else
+                warning('Sorting was not set properly. Data returned as single long line');
+            end
+        end
+
+        function [data, notype] = put_traces(filename, data, n1, n2, notype)
+            if exist(filename, 'file') ~= 2
+                error('File does not exist')
+            end
+
+            if nargin < 2
+                error('Too few arguments. Usage: put_traces( filename, data, (optional): n1, n2, notype)')
+            end
+
+            if nargin < 3
+                n1 = 1;
+            end
+
+            if nargin < 4
+                n2 = 0;
+            end
+
+            if nargin < 5
+                notype = -1;
+            end
+
+            % matlab uses 1-indexing, but C wants its positions 0-indexed.
+            [data, notype] = segy_put_traces_mex( filename, data, n1 - 1, n2 - 1, notype );
+        end
+
+
+        % function SegyHeader = get_segy_header(filename)
+        %
+        % Goal:
+        %   Read segy header. Extended textual headers are not read
+        %
+        % Inputs:
+        %   filename    Filename of segyfile
+        %
+        % Output:
+        %   SegyHeader  Struct with entire segy header
+        function SegyHeader = get_segy_header(filename)
+            if exist(filename, 'file') ~= 2
+                error('File does not exist')
+            end
+
+            [ebcdic, bin] = segy_get_segy_header_mex( filename );
+
+            SegyHeader.ebcdic = ebcdic;
+            SegyHeader.JobIdNumber = segy_get_bfield_mex( bin, 3201 );
+            SegyHeader.LineNumber = segy_get_bfield_mex( bin, 3205 );
+            SegyHeader.ReelNumber = segy_get_bfield_mex( bin, 3209 );
+            SegyHeader.NumberOfTracesPerEnsemble = segy_get_bfield_mex( bin, 3213 );
+            SegyHeader.NumberOfAuxTracesPerEnsemble = segy_get_bfield_mex( bin, 3215 );
+            SegyHeader.SampleInterval = segy_get_bfield_mex( bin, 3217 );
+            SegyHeader.SampleIntervalOriginal = segy_get_bfield_mex( bin, 3219 );
+            SegyHeader.NumberOfSamples = segy_get_bfield_mex( bin, 3221 );
+            SegyHeader.NumberOfSamplesOriginal = segy_get_bfield_mex( bin, 3223 );
+
+            SegyHeader.SampleFormat = 0;
+            switch segy_get_bfield_mex( bin, 3225 )
+                case 1
+                    SegyHeader.SampleFormat = 'IBM32';
+                case 2
+                    SegyHeader.SampleFormat = 'INT32';
+                case 3
+                    SegyHeader.SampleFormat = 'INT16';
+                case 4
+                    SegyHeader.SampleFormat = 'Obsolete';
+                case 5
+                    SegyHeader.SampleFormat = 'IEEE32';
+                case 6
+                    SegyHeader.SampleFormat = 'NotUsed';
+                case 7
+                    SegyHeader.SampleFormat = 'NotUsed';
+                case 8
+                    SegyHeader.SampleFormat = 'INT8';
+            end
+
+            SegyHeader.EnsembleFold = segy_get_bfield_mex( bin, 3227 );
+
+            SegyHeader.TraceSortingCode = 0;
+            switch segy_get_bfield_mex( bin, 3229 )
+                case -1
+                    SegyHeader.TraceSortingCode = 'Other';
+                case 0
+                    SegyHeader.TraceSortingCode = 'Unknown';
+                case 1
+                    SegyHeader.TraceSortingCode = 'AsRecorded';
+                case 2
+                    SegyHeader.TraceSortingCode = 'CDP';
+                case 3
+                    SegyHeader.TraceSortingCode = 'SingleFoldContinuousProfile';
+                case 4
+                    SegyHeader.TraceSortingCode = 'HorizontallyStacked';
+                case 5
+                    SegyHeader.TraceSortingCode = 'CommonSourcePoint';
+                case 6
+                    SegyHeader.TraceSortingCode = 'CommonReceiverPoint';
+                case 7
+                    SegyHeader.TraceSortingCode = 'CommonOffsetPoint';
+                case 8
+                    SegyHeader.TraceSortingCode = 'CommonMidPoint';
+                case 9
+                    SegyHeader.TraceSortingCode = 'CommonConversionPoint';
+            end
+
+            SegyHeader.VerticalSumCode = [num2str(segy_get_bfield_mex( bin, 3231 )), '_Sum'];
+            SegyHeader.SweepFrequencyAtStart = segy_get_bfield_mex( bin, 3233 );
+            SegyHeader.SweepFrequencyAtEnd = segy_get_bfield_mex( bin, 3235 );
+            SegyHeader.SweepLength = segy_get_bfield_mex( bin, 3237 );
+            SegyHeader.SweepTypeCode = 0;
+            switch segy_get_bfield_mex( bin, 3239 )
+                case 1
+                    SegyHeader.SweepTypeCode = 'Linear';
+                case 2
+                    SegyHeader.SweepTypeCode = 'Parabolic';
+                case 3
+                    SegyHeader.SweepTypeCode = 'Exponential';
+                case 4
+                    SegyHeader.SweepTypeCode = 'Other';
+            end
+
+            SegyHeader.TraceNoOfSweepChannel = segy_get_bfield_mex( bin, 3241 );
+            SegyHeader.SweepTraceTaperLenghtStart = segy_get_bfield_mex( bin, 3243 );
+            SegyHeader.SweepTraceTaperLenghtEnd = segy_get_bfield_mex( bin, 3245 );
+
+            SegyHeader.TaperType = 0;
+            switch segy_get_bfield_mex( bin, 3247 )
+                    case 1
+                        SegyHeader.TaperType = 'Linear';
+                    case 2
+                        SegyHeader.TaperType = 'Cos^2';
+                    case 3
+                        SegyHeader.TaperType = 'Other';
+            end
+
+            SegyHeader.CorrelatedDataTraces = 0;
+            switch segy_get_bfield_mex( bin, 3249 )
+                case 1
+                    SegyHeader.CorrelatedDataTraces = 'No';
+                case 2
+                    SegyHeader.CorrelatedDataTraces = 'Yes';
+            end
+
+            SegyHeader.BinaryGainRecovered = 0;
+            switch segy_get_bfield_mex( bin, 3251 )
+                case 1
+                    SegyHeader.BinaryGainRecovered = 'Yes';
+                case 2
+                    SegyHeader.BinaryGainRecovered = 'No';
+            end
+
+            SegyHeader.AmplitudeRecoveryMethod = 0;
+            switch segy_get_bfield_mex( bin, 3253 )
+                case 1
+                    SegyHeader.AmplitudeRecoveryMethod = 'None';
+                case 2
+                    SegyHeader.AmplitudeRecoveryMethod = 'SphericalDivergence';
+                case 3
+                    SegyHeader.AmplitudeRecoveryMethod = 'AGC';
+                case 4
+                    SegyHeader.AmplitudeRecoveryMethod = 'Other';
+            end
+
+            SegyHeader.MeasurementSystem = 0;
+            switch segy_get_bfield_mex( bin, 3255 )
+                case 1
+                    SegyHeader.MeasurementSystem = 'Meter';
+                case 2
+                    SegyHeader.MeasurementSystem = 'Feet';
+            end
+
+            SegyHeader.ImpulseSignalPolarity = 0;
+            switch segy_get_bfield_mex( bin, 3257 )
+                case 1
+                    SegyHeader.ImpulseSignalPolarity = 'IncreasePressureNegativeNumber';
+                case 2
+                    SegyHeader.ImpulseSignalPolarity = 'IncreasePressurePositiveNumber';
+            end
+
+            SegyHeader.VibratorPolarityCode = 0;
+            switch segy_get_bfield_mex( bin, 3259 )
+                case 1
+                    SegyHeader.VibratorPolarityCode = '337.5-22.5';
+                case 2
+                    SegyHeader.VibratorPolarityCode = '22.5-67.5';
+                case 3
+                    SegyHeader.VibratorPolarityCode = '67.5-112.5';
+                case 4
+                    SegyHeader.VibratorPolarityCode = '112.5-157.5';
+                case 5
+                    SegyHeader.VibratorPolarityCode = '157.5-202.5';
+                case 6
+                    SegyHeader.VibratorPolarityCode = '202.5-247.5';
+                case 7
+                    SegyHeader.VibratorPolarityCode = '247.5-292.5';
+                case 8
+                    SegyHeader.VibratorPolarityCode = '292.5-337.5';
+            end
+
+            SegyHeader.FormatRevisionNumber = segy_get_bfield_mex( bin, 3501 );
+            SegyHeader.FixedLengthTraceFlag = segy_get_bfield_mex( bin, 3503 );
+            SegyHeader.NumberOfExtTextHeaders = segy_get_bfield_mex( bin, 3505 );
+        end
+
+        % [tr_heads,notrace] = get_trace_header(filename,itrace);
+        %
+        % Goal:
+        %   Read the full trace header of the trace itrace
+        %
+        % Inputs:
+        %   filename    Filename of segyfile
+        %   itrace      trace number
+        %
+        % Output:
+        %   tr_heads    Header values
+        %   notrace     number of traces in segy file
+        %
+        function [tr_heads, notrace] = get_trace_header(filename, itrace)
+            [header, notrace] = segy_get_trace_header_mex( filename, itrace );
+
+            % read trace header
+            tr_heads.TraceSequenceLine                      = segy_get_field_mex( header, 1 );
+            tr_heads.TraceSequenceFile                      = segy_get_field_mex( header, 5 );
+            tr_heads.FieldRecord                            = segy_get_field_mex( header, 9 );
+            tr_heads.TraceNumber                            = segy_get_field_mex( header, 13 );
+            tr_heads.EnergySourcePoint                      = segy_get_field_mex( header, 17 );
+            tr_heads.cdp                                    = segy_get_field_mex( header, 21 );
+            tr_heads.cdpTrace                               = segy_get_field_mex( header, 25 );
+            tr_heads.TraceIdenitifactionCode                = segy_get_field_mex( header, 29 );
+            tr_heads.NSummedTraces                          = segy_get_field_mex( header, 31 );
+            tr_heads.NStackedTraces                         = segy_get_field_mex( header, 33 );
+            tr_heads.DataUse                                = segy_get_field_mex( header, 35 );
+            tr_heads.offset                                 = segy_get_field_mex( header, 37 );
+            tr_heads.ReceiverGroupElevation                 = segy_get_field_mex( header, 41 );
+            tr_heads.SourceSurfaceElevation                 = segy_get_field_mex( header, 45 );
+            tr_heads.SourceDepth                            = segy_get_field_mex( header, 49 );
+            tr_heads.ReceiverDatumElevation                 = segy_get_field_mex( header, 53 );
+            tr_heads.SourceDatumElevation                   = segy_get_field_mex( header, 57 );
+            tr_heads.SourceWaterDepth                       = segy_get_field_mex( header, 61 );
+            tr_heads.GroupWaterDepth                        = segy_get_field_mex( header, 65 );
+            tr_heads.ElevationScalar                        = segy_get_field_mex( header, 69 );
+            tr_heads.SourceGroupScalar                      = segy_get_field_mex( header, 71 );
+            tr_heads.SourceX                                = segy_get_field_mex( header, 73 );
+            tr_heads.SourceY                                = segy_get_field_mex( header, 77 );
+            tr_heads.GroupX                                 = segy_get_field_mex( header, 81 );
+            tr_heads.GroupY                                 = segy_get_field_mex( header, 85 );
+            tr_heads.CoordinateUnits                        = segy_get_field_mex( header, 89 );
+            tr_heads.WeatheringVelocity                     = segy_get_field_mex( header, 91 );
+            tr_heads.SubWeatheringVelocity                  = segy_get_field_mex( header, 93 );
+            tr_heads.SourceUpholeTime                       = segy_get_field_mex( header, 95 );
+            tr_heads.GroupUpholeTime                        = segy_get_field_mex( header, 97 );
+            tr_heads.SourceStaticCorrection                 = segy_get_field_mex( header, 99 );
+            tr_heads.GroupStaticCorrection                  = segy_get_field_mex( header, 101 );
+            tr_heads.TotalStaticApplied                     = segy_get_field_mex( header, 103 );
+            tr_heads.LagTimeA                               = segy_get_field_mex( header, 105 );
+            tr_heads.LagTimeB                               = segy_get_field_mex( header, 107 );
+            tr_heads.DelayRecordingTime                     = segy_get_field_mex( header, 109 );
+            tr_heads.MuteTimeStart                          = segy_get_field_mex( header, 111 );
+            tr_heads.MuteTimeEND                            = segy_get_field_mex( header, 113 );
+            tr_heads.ns                                     = segy_get_field_mex( header, 115 );
+            tr_heads.dt                                     = segy_get_field_mex( header, 117 );
+            tr_heads.GainType                               = segy_get_field_mex( header, 119 );
+            tr_heads.InstrumentGainConstant                 = segy_get_field_mex( header, 121 );
+            tr_heads.InstrumentInitialGain                  = segy_get_field_mex( header, 123 );
+            tr_heads.Correlated                             = segy_get_field_mex( header, 125 );
+            tr_heads.SweepFrequenceStart                    = segy_get_field_mex( header, 127 );
+            tr_heads.SweepFrequenceEnd                      = segy_get_field_mex( header, 129 );
+            tr_heads.SweepLength                            = segy_get_field_mex( header, 131 );
+            tr_heads.SweepType                              = segy_get_field_mex( header, 133 );
+            tr_heads.SweepTraceTaperLengthStart             = segy_get_field_mex( header, 135 );
+            tr_heads.SweepTraceTaperLengthEnd               = segy_get_field_mex( header, 137 );
+            tr_heads.TaperType                              = segy_get_field_mex( header, 139 );
+            tr_heads.AliasFilterFrequency                   = segy_get_field_mex( header, 141 );
+            tr_heads.AliasFilterSlope                       = segy_get_field_mex( header, 143 );
+            tr_heads.NotchFilterFrequency                   = segy_get_field_mex( header, 145 );
+            tr_heads.NotchFilterSlope                       = segy_get_field_mex( header, 147 );
+            tr_heads.LowCutFrequency                        = segy_get_field_mex( header, 149 );
+            tr_heads.HighCutFrequency                       = segy_get_field_mex( header, 151 );
+            tr_heads.LowCutSlope                            = segy_get_field_mex( header, 153 );
+            tr_heads.HighCutSlope                           = segy_get_field_mex( header, 155 );
+            tr_heads.YearDataRecorded                       = segy_get_field_mex( header, 157 );
+            tr_heads.DayOfYear                              = segy_get_field_mex( header, 159 );
+            tr_heads.HourOfDay                              = segy_get_field_mex( header, 161 );
+            tr_heads.MinuteOfHour                           = segy_get_field_mex( header, 163 );
+            tr_heads.SecondOfMinute                         = segy_get_field_mex( header, 165 );
+            tr_heads.TimeBaseCode                           = segy_get_field_mex( header, 167 );
+            tr_heads.TraceWeightningFactor                  = segy_get_field_mex( header, 169 );
+            tr_heads.GeophoneGroupNumberRoll1               = segy_get_field_mex( header, 171 );
+            tr_heads.GeophoneGroupNumberFirstTraceOrigField = segy_get_field_mex( header, 173 );
+            tr_heads.GeophoneGroupNumberLastTraceOrigField  = segy_get_field_mex( header, 175 );
+            tr_heads.GapSize                                = segy_get_field_mex( header, 177 );
+            tr_heads.OverTravel                             = segy_get_field_mex( header, 179 );
+            tr_heads.cdpX                                   = segy_get_field_mex( header, 181 );
+            tr_heads.cdpY                                   = segy_get_field_mex( header, 185 );
+            tr_heads.Inline3D                               = segy_get_field_mex( header, 189 );
+            tr_heads.Crossline3D                            = segy_get_field_mex( header, 193 );
+            tr_heads.ShotPoint                              = segy_get_field_mex( header, 197 );
+            tr_heads.ShotPointScalar                        = segy_get_field_mex( header, 201 );
+            tr_heads.TraceValueMeasurementUnit              = segy_get_field_mex( header, 203 );
+            tr_heads.TransductionConstantMantissa           = segy_get_field_mex( header, 205 );
+            tr_heads.TransductionConstantPower              = segy_get_field_mex( header, 209 );
+            tr_heads.TransductionUnit                       = segy_get_field_mex( header, 211 );
+            tr_heads.TraceIdentifier                        = segy_get_field_mex( header, 213 );
+            tr_heads.ScalarTraceHeader                      = segy_get_field_mex( header, 215 );
+            tr_heads.SourceType                             = segy_get_field_mex( header, 217 );
+            tr_heads.SourceEnergyDirectionMantissa          = segy_get_field_mex( header, 219 );
+            tr_heads.SourceEnergyDirectionExponent          = segy_get_field_mex( header, 223 );
+            tr_heads.SourceMeasurementMantissa              = segy_get_field_mex( header, 225 );
+            tr_heads.SourceMeasurementExponent              = segy_get_field_mex( header, 229 );
+            tr_heads.SourceMeasurementUnit                  = segy_get_field_mex( header, 231 );
+            tr_heads.UnassignedInt1                         = segy_get_field_mex( header, 233 );
+            tr_heads.UnassignedInt2                         = segy_get_field_mex( header, 237 );
+        end
+
+        % put_headers(filename,headers,headword)
+        %
+        % Goal:
+        %   Fast writing of trace header words in segy file.
+        %
+        % Inputs:
+        %   filename    Filename of segyfile
+        %   headers     Array of headervalues or single headervalue. Will be
+        %               written to all trace headers. If length of array is
+        %               different from number of traces in file an error will be
+        %               thrown.
+        %   headword    Name of header word to be written (example: 'cdp')
+        %
+        function put_headers(filename, headers, headword)
+            ntraces = Segy.get_ntraces( filename );
+            if and( ~isscalar(headers), max(size( headers )) ~= ntraces )
+                error( 'Inconsistent dimensions of header values' )
+            end
+
+            % if single header value, create a traces-sized vector of that
+            % header value, so that segy_put_headers_mex can always assume array
+            if isscalar( headers )
+                headers = ones( 1, ntraces ) * headers;
+            end
+
+            if ischar(headword)
+                headword = TraceField.(headword);
+            end
+
+            segy_put_headers_mex( filename, headers, int32(headword) );
+        end
+    end
+end
diff --git a/mex/SegySampleFormat.m b/mex/SegySampleFormat.m
new file mode 100644
index 0000000..86beca6
--- /dev/null
+++ b/mex/SegySampleFormat.m
@@ -0,0 +1,13 @@
+classdef SegySampleFormat < int32
+% add comment here
+    enumeration
+        IBM_FLOAT_4_BYTE (1)
+        SIGNED_INTEGER_4_BYTE (2)
+        SIGNED_SHORT_2_BYTE (3)
+        FIXED_POINT_WITH_GAIN_4_BYTE (4)
+        IEEE_FLOAT_4_BYTE (5)
+        NOT_IN_USE_1 (6)
+        NOT_IN_USE_2 (7)
+        SIGNED_CHAR_1_BYTE (8)
+    end
+end
diff --git a/mex/SegySpec.m b/mex/SegySpec.m
new file mode 100644
index 0000000..480eed4
--- /dev/null
+++ b/mex/SegySpec.m
@@ -0,0 +1,48 @@
+classdef SegySpec
+% add comment here
+    properties
+        filename
+        sample_format
+        trace_sorting_format
+        sample_indexes
+        crossline_indexes
+        inline_indexes
+        offset_count
+        first_trace_pos
+        il_stride
+        xl_stride
+        trace_bsize
+        t
+        iline
+        xline
+    end
+
+    methods
+
+        function obj = SegySpec(filename, inline_field, crossline_field, t0)
+            spec = segyspec_mex(filename, int32(inline_field), int32(crossline_field), t0);
+            obj.filename = filename;
+
+            if (isempty(spec))
+                e = MException('SegySpec:NoSuchFile', 'File %s not found',filename);
+                throw(e);
+            end
+
+            obj.sample_format = uint32(SegySampleFormat(spec.sample_format));
+            obj.trace_sorting_format = TraceSortingFormat(spec.trace_sorting_format);
+            obj.sample_indexes = spec.sample_indexes;
+            obj.crossline_indexes = uint32(spec.crossline_indexes);
+            obj.inline_indexes = uint32(spec.inline_indexes);
+            obj.offset_count = uint32(spec.offset_count);
+            obj.first_trace_pos = uint32(spec.first_trace_pos);
+            obj.il_stride = spec.il_stride;
+            obj.xl_stride = spec.xl_stride;
+            obj.trace_bsize = spec.trace_bsize;
+            obj.t = obj.sample_indexes;
+            obj.iline = obj.inline_indexes;
+            obj.xline = obj.crossline_indexes;
+        end
+
+    end
+
+end
diff --git a/mex/TraceField.m b/mex/TraceField.m
new file mode 100644
index 0000000..410875a
--- /dev/null
+++ b/mex/TraceField.m
@@ -0,0 +1,96 @@
+classdef TraceField < int32
+    % add comment here
+    enumeration
+        TRACE_SEQUENCE_LINE (1)
+        TRACE_SEQUENCE_FILE (5)
+        FieldRecord (9)
+        TraceNumber (13)
+        EnergySourcePoint (17)
+        CDP (21)
+        CDP_TRACE (25)
+        TraceIdentificationCode (29)
+        NSummedTraces (31)
+        NStackedTraces (33)
+        DataUse (35)
+        offset (37)
+        ReceiverGroupElevation (41)
+        SourceSurfaceElevation (45)
+        SourceDepth (49)
+        ReceiverDatumElevation (53)
+        SourceDatumElevation (57)
+        SourceWaterDepth (61)
+        GroupWaterDepth (65)
+        ElevationScalar (69)
+        SourceGroupScalar (71)
+        SourceX (73)
+        SourceY (77)
+        GroupX (81)
+        GroupY (85)
+        CoordinateUnits (89)
+        WeatheringVelocity (91)
+        SubWeatheringVelocity (93)
+        SourceUpholeTime (95)
+        GroupUpholeTime (97)
+        SourceStaticCorrection (99)
+        GroupStaticCorrection (101)
+        TotalStaticApplied (103)
+        LagTimeA (105)
+        LagTimeB (107)
+        DelayRecordingTime (109)
+        MuteTimeStart (111)
+        MuteTimeEND (113)
+        TRACE_SAMPLE_COUNT (115)
+        TRACE_SAMPLE_INTERVAL (117)
+        GainType (119)
+        InstrumentGainConstant (121)
+        InstrumentInitialGain (123)
+        Correlated (125)
+        SweepFrequencyStart (127)
+        SweepFrequencyEnd (129)
+        SweepLength (131)
+        SweepType (133)
+        SweepTraceTaperLengthStart (135)
+        SweepTraceTaperLengthEnd (137)
+        TaperType (139)
+        AliasFilterFrequency (141)
+        AliasFilterSlope (143)
+        NotchFilterFrequency (145)
+        NotchFilterSlope (147)
+        LowCutFrequency (149)
+        HighCutFrequency (151)
+        LowCutSlope (153)
+        HighCutSlope (155)
+        YearDataRecorded (157)
+        DayOfYear (159)
+        HourOfDay (161)
+        MinuteOfHour (163)
+        SecondOfMinute (165)
+        TimeBaseCode (167)
+        TraceWeightingFactor (169)
+        GeophoneGroupNumberRoll1 (171)
+        GeophoneGroupNumberFirstTraceOrigField (173)
+        GeophoneGroupNumberLastTraceOrigField (175)
+        GapSize (177)
+        OverTravel (179)
+        CDP_X (181)
+        CDP_Y (185)
+        INLINE_3D (189)
+        CROSSLINE_3D (193)
+        ShotPoint (197)
+        ShotPointScalar (201)
+        TraceValueMeasurementUnit (203)
+        TransductionConstantMantissa (205)
+        TransductionConstantPower (209)
+        TransductionUnit (211)
+        TraceIdentifier (213)
+        ScalarTraceHeader (215)
+        SourceType (217)
+        SourceEnergyDirectionMantissa (219)
+        SourceEnergyDirectionExponent (223)
+        SourceMeasurementMantissa (225)
+        SourceMeasurementExponent (229)
+        SourceMeasurementUnit (231)
+        UnassignedInt1 (233)
+        UnassignedInt2 (237)
+    end
+end
\ No newline at end of file
diff --git a/mex/TraceSortingFormat.m b/mex/TraceSortingFormat.m
new file mode 100644
index 0000000..f26a250
--- /dev/null
+++ b/mex/TraceSortingFormat.m
@@ -0,0 +1,9 @@
+classdef TraceSortingFormat < int32
+% add comment here
+    enumeration
+        UNKNOWN_SORTING (0)
+        XLINE (1)
+        INLINE (2)
+    end
+end
+
diff --git a/mex/get_line.c b/mex/get_line.c
new file mode 100644
index 0000000..173477d
--- /dev/null
+++ b/mex/get_line.c
@@ -0,0 +1,41 @@
+#include <segyio/segy.h>
+
+#include "mex.h"
+
+/*
+ * get_line.c
+ *
+ * get_line( cube, dir, n )
+ */
+
+void mexFunction( int nlhs, mxArray* plhs[],
+                  int nrhs, mxArray* prhs[] ) {
+
+    if( nhrs != 3 ) {
+        mxErrMsgIdAndTxt("segy:get_line:nrhs", "3 input arguments required: cube, dir, n");
+    }
+
+    if( nlhs != 1 ) {
+        mxErrMsgIdAndTxt("segy:get_line:nrhs", "1 arguments required: line");
+    }
+
+    int err;
+    const mxArray* cube = prhs[0];
+    const mxArray* dir  = prhs[1];
+    const mxArray* n    = prhs[2];
+
+    const size_t dir_arg_size = sizeof( "iline" );
+    char* dir_c = malloc( dir_arg_size );
+    err = mxGetString( dir, dir_c dir_arg_size );
+
+    if( err != 0 ) {
+        mxErrMsgIdAndTxt("segy:get_line:strcpy", "Failure parsing direction argument" );
+    }
+
+    if( strncmp( dir_c, "iline", dir_arg_size ) != 0
+     && strncmp( dir_c, "xline", dir_arg_size ) ) {
+        mxErrMsgIdAndTxt("segy:get_line:dir", "Invalid direction. Valid directions: 'iline', 'xline'");
+    }
+    const bool iline = strncmp( dir_c, "iline", dir_arg_size ) == 0;
+
+}
diff --git a/mex/segy_get_bfield_mex.c b/mex/segy_get_bfield_mex.c
new file mode 100644
index 0000000..6d724f5
--- /dev/null
+++ b/mex/segy_get_bfield_mex.c
@@ -0,0 +1,24 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    const char* bin = mxGetData( prhs[ 0 ] );
+    const int field = mxGetScalar( prhs[ 1 ] );
+    int f;
+
+    int err = segy_get_bfield( bin, field, &f );
+
+    if( err == SEGY_INVALID_FIELD )
+        mexErrMsgIdAndTxt( "segy:get_bfield:invalid_field",
+                           "Invalid field value/header offset" );
+
+    plhs[ 0 ] = mxCreateDoubleScalar( f );
+}
diff --git a/mex/segy_get_field_mex.c b/mex/segy_get_field_mex.c
new file mode 100644
index 0000000..fd243f1
--- /dev/null
+++ b/mex/segy_get_field_mex.c
@@ -0,0 +1,24 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    const char* bin = mxGetData( prhs[ 0 ] );
+    const int field = mxGetScalar( prhs[ 1 ] );
+    int f;
+
+    int err = segy_get_field( bin, field, &f );
+
+    if( err == SEGY_INVALID_FIELD )
+        mexErrMsgIdAndTxt( "segy:get_field:invalid_field",
+                           "Invalid field value/header offset" );
+
+    plhs[ 0 ] = mxCreateDoubleScalar( f );
+}
diff --git a/mex/segy_get_header_mex.c b/mex/segy_get_header_mex.c
new file mode 100644
index 0000000..8cf3808
--- /dev/null
+++ b/mex/segy_get_header_mex.c
@@ -0,0 +1,61 @@
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    char* msg1;
+    char* msg2;
+    int err;
+
+    const char* filename = mxArrayToString( prhs[ 0 ] );
+    FILE* fp = segyfopen( prhs[ 0 ], "r" );
+
+    int field = mxGetScalar( prhs[ 1 ] );
+
+    struct segy_file_format fmt = filefmt( fp );
+
+    plhs[0] = mxCreateNumericMatrix( 1, fmt.traces, mxINT32_CLASS, mxREAL );
+    int32_t* out = mxGetData( plhs[ 0 ] );
+
+    char header[ SEGY_TRACE_HEADER_SIZE ];
+
+    for( size_t i = 0; i < fmt.traces; ++i ) {
+        int32_t f;
+        err = segy_traceheader( fp, i, header, fmt.trace0, fmt.trace_bsize );
+
+        if( err != 0 ) {
+            msg1 = "segy:get_header:segy_traceheader";
+            msg2 = strerror( errno );
+            goto cleanup;
+        }
+
+        err = segy_get_field( header, field, &f );
+
+        if( err != 0 ) {
+            msg1 = "segy:get_header:segy_get_field";
+            msg2 = "Error reading header field.";
+            goto cleanup;
+        }
+
+        out[ i ] = f;
+    }
+
+    fclose( fp );
+
+    plhs[ 1 ] = mxCreateDoubleScalar( fmt.traces );
+    return;
+
+cleanup:
+    fclose( fp );
+
+cleanup_fopen:
+    mexErrMsgIdAndTxt( msg1, msg2 );
+}
diff --git a/mex/segy_get_ntraces_mex.c b/mex/segy_get_ntraces_mex.c
new file mode 100644
index 0000000..7f35172
--- /dev/null
+++ b/mex/segy_get_ntraces_mex.c
@@ -0,0 +1,14 @@
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    FILE* fp = segyfopen( prhs[ 0 ], "r" );
+    struct segy_file_format fmt = filefmt( fp );
+    fclose( fp );
+
+    plhs[0] = mxCreateDoubleScalar( fmt.traces );
+}
diff --git a/mex/segy_get_segy_header_mex.c b/mex/segy_get_segy_header_mex.c
new file mode 100644
index 0000000..d804074
--- /dev/null
+++ b/mex/segy_get_segy_header_mex.c
@@ -0,0 +1,44 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    char* msg1;
+    char* msg2;
+    int err;
+
+    FILE* fp = segyfopen( prhs[ 0 ], "r" );
+    char* textheader = mxMalloc( segy_textheader_size() );
+    err = segy_textheader( fp, textheader );
+    
+    if( err != 0 ) {
+        msg1 = "segy:text_header:os";
+        msg2 = strerror( errno );
+        goto cleanup;
+    }
+    plhs[ 0 ] = mxCreateString( textheader );
+
+    mwSize dims[ 1 ] = { segy_binheader_size() };
+    plhs[ 1 ] = mxCreateCharArray( 1, dims );
+    err = segy_binheader( fp, mxGetData( plhs[ 1 ] ) );
+
+    if( err != 0 ) {
+        msg1 = "segy:binary_header:os";
+        msg2 = strerror( errno );
+        goto cleanup;
+    }
+
+    mxFree( textheader );
+    return;
+
+cleanup:
+    fclose( fp );
+    mexErrMsgIdAndTxt( msg1, msg2 );
+}
diff --git a/mex/segy_get_trace_header_mex.c b/mex/segy_get_trace_header_mex.c
new file mode 100644
index 0000000..d46bebd
--- /dev/null
+++ b/mex/segy_get_trace_header_mex.c
@@ -0,0 +1,42 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    char* msg1;
+    char* msg2;
+    int err;
+
+    FILE* fp = segyfopen( prhs[ 0 ], "r" );
+    struct segy_file_format fmt = filefmt( fp );
+    int traceno = mxGetScalar( prhs[ 1 ] );
+
+    if( traceno > fmt.traces )
+        mexErrMsgIdAndTxt( "segy:get_trace_header:bounds",
+                           "Requested trace header does not exist in this file." );
+
+    mwSize dims[ 1 ] = { SEGY_TRACE_HEADER_SIZE };
+    plhs[ 0 ] = mxCreateCharArray( 1, dims );
+    err = segy_traceheader( fp, traceno, mxGetData( plhs[ 0 ] ), fmt.trace0, fmt.trace_bsize );
+
+    fclose( fp );
+
+    if( err != 0 ) {
+        msg1 = "segy:get_trace_header:os";
+        msg2 = strerror( errno );
+        goto cleanup;
+    }
+
+    plhs[ 1 ] = mxCreateDoubleScalar( fmt.traces );
+    return;
+
+cleanup:
+    mexErrMsgIdAndTxt( msg1, msg2 );
+}
diff --git a/mex/segy_get_traces_mex.c b/mex/segy_get_traces_mex.c
new file mode 100644
index 0000000..7f04589
--- /dev/null
+++ b/mex/segy_get_traces_mex.c
@@ -0,0 +1,76 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    char* msg1;
+    char* msg2;
+    int err;
+
+    FILE* fp = segyfopen( prhs[ 0 ], "r" );
+    int first_trace = mxGetScalar( prhs[ 1 ] );
+    int last_trace  = mxGetScalar( prhs[ 2 ] );
+    int notype      = mxGetScalar( prhs[ 3 ] );
+
+    struct segy_file_format fmt = filefmt( fp );
+
+    char binary[ SEGY_BINARY_HEADER_SIZE ];
+    err = segy_binheader( fp, binary );
+    if( err != 0 ) {
+        msg1 = "segy:get_traces:binary";
+        msg2 = strerror( errno );
+        goto cleanup;
+    }
+
+    if( last_trace != -1 && last_trace < fmt.traces )
+        fmt.traces = (last_trace + 1);
+
+    fmt.traces -= first_trace;
+
+    plhs[0] = mxCreateNumericMatrix( fmt.samples, fmt.traces, mxSINGLE_CLASS, mxREAL );
+    float* out = mxGetData( plhs[ 0 ] );
+
+    if( first_trace > fmt.traces ) {
+        msg1 = "segy:get_traces:bounds";
+        msg2 = "first trace must be smaller than last trace";
+        goto cleanup;
+    }
+
+    for( size_t i = first_trace; i < fmt.traces; ++i ) {
+        err = segy_readtrace( fp, i, out, fmt.trace0, fmt.trace_bsize );
+        out += fmt.samples;
+
+        if( err != 0 ) {
+            msg1 = "segy:get_traces:segy_readtrace";
+            msg2 = strerror( errno );
+            goto cleanup;
+        }
+    }
+
+    fclose( fp );
+
+    if( notype != -1 )
+        fmt.format = notype;
+
+    segy_to_native( fmt.format, fmt.samples * fmt.traces, mxGetData( plhs[ 0 ] ) );
+
+    int interval;
+    segy_get_bfield( binary, BIN_Interval, &interval );
+    plhs[ 1 ] = mxCreateDoubleScalar( interval );
+    plhs[ 2 ] = mxCreateDoubleScalar( fmt.format );
+
+    return;
+
+cleanup:
+    fclose( fp );
+
+cleanup_fopen:
+    mexErrMsgIdAndTxt( msg1, msg2 );
+}
diff --git a/mex/segy_interpret_segycube_mex.c b/mex/segy_interpret_segycube_mex.c
new file mode 100644
index 0000000..bc8d829
--- /dev/null
+++ b/mex/segy_interpret_segycube_mex.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
diff --git a/mex/segy_put_headers_mex.c b/mex/segy_put_headers_mex.c
new file mode 100644
index 0000000..abba4db
--- /dev/null
+++ b/mex/segy_put_headers_mex.c
@@ -0,0 +1,62 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    char* msg1;
+    char* msg2;
+    int err;
+
+    FILE* fp = segyfopen( prhs[ 0 ], "r+" );
+    double* headers = mxGetPr( prhs[ 1 ] );
+    int field = mxGetScalar( prhs[ 2 ] );
+
+    struct segy_file_format fmt = filefmt( fp );
+
+    char traceheader[ SEGY_TRACE_HEADER_SIZE ];
+    /* 
+     * check that the field is valid and writing it won't return an error. by
+     * checking it here we don't have to do it in the write loop
+     */
+    err = segy_set_field( traceheader, field, 0 );
+
+    if( err != 0 ) {
+        msg1 = "segy:put_headers:invalid_field";
+        msg2 = "Invalid field value/header offset";
+        goto cleanup;
+    }
+
+    double* itr = headers;
+    for( size_t i = 0; i < fmt.traces; ++i, ++itr ) {
+        err = segy_traceheader( fp, i, traceheader, fmt.trace0, fmt.trace_bsize );
+        const int val = *itr;
+
+        if( err != 0 ) {
+            msg1 = "segy:put_headers:os";
+            msg2 = strerror( errno );
+            goto cleanup;
+        }
+
+        segy_set_field( traceheader, field, val );
+        err = segy_write_traceheader( fp, i, traceheader, fmt.trace0, fmt.trace_bsize );
+        if( err != 0 ) {
+            msg1 = "segy:put_headers:os";
+            msg2 = strerror( errno );
+            goto cleanup;
+        }
+    }
+
+    fclose( fp );
+    return;
+
+cleanup:
+    fclose( fp );
+    mexErrMsgIdAndTxt( msg1, msg2 );
+}
diff --git a/mex/segy_put_traces_mex.c b/mex/segy_put_traces_mex.c
new file mode 100644
index 0000000..df317dd
--- /dev/null
+++ b/mex/segy_put_traces_mex.c
@@ -0,0 +1,69 @@
+#include <errno.h>
+#include <string.h>
+
+#include <segyio/segy.h>
+#include "segyutil.h"
+
+#include "matrix.h"
+#include "mex.h"
+
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    char* msg1;
+    char* msg2;
+    int err;
+
+    FILE* fp = segyfopen( prhs[ 0 ], "r+" );
+    plhs[ 0 ] = mxDuplicateArray( prhs[ 1 ] );
+    int first_trace = mxGetScalar( prhs[ 2 ] );
+    int last_trace  = mxGetScalar( prhs[ 3 ] );
+    int notype      = mxGetScalar( prhs[ 4 ] );
+
+    char binary[ SEGY_BINARY_HEADER_SIZE ];
+    struct segy_file_format fmt = filefmt( fp );
+
+    if( notype != -1 )
+        fmt.format = notype;
+
+    if( last_trace != 1 && last_trace < fmt.traces )
+        fmt.traces = (last_trace + 1);
+
+    fmt.traces -= first_trace;
+
+    float* out      = mxGetData( plhs[ 0 ] );
+    segy_from_native( fmt.format, fmt.samples * fmt.traces, out );
+
+    if( first_trace > fmt.traces ) {
+        msg1 = "segy:get_traces:bounds";
+        msg2 = "first trace must be smaller than last trace";
+        goto cleanup;
+    }
+
+    float* itr = out;
+    for( size_t i = first_trace; i < fmt.traces; ++i ) {
+        err = segy_writetrace( fp, i, itr, fmt.trace0, fmt.trace_bsize );
+        itr += fmt.samples;
+
+        if( err != 0 ) {
+            msg1 = "segy:put_traces:segy_writetrace";
+            msg2 = strerror( errno );
+            fmt.traces = i;
+            goto cleanup;
+        }
+    }
+
+    fclose( fp );
+
+    segy_to_native( fmt.format, fmt.samples * fmt.traces, out );
+
+    plhs[ 1 ] = mxCreateDoubleScalar( fmt.format );
+
+    return;
+
+cleanup:
+    fclose( fp );
+    segy_to_native( fmt.format, fmt.samples * fmt.traces, out );
+
+    mexErrMsgIdAndTxt( msg1, msg2 );
+}
diff --git a/mex/segy_read_write_line_mex.c b/mex/segy_read_write_line_mex.c
new file mode 100644
index 0000000..5bbf9a2
--- /dev/null
+++ b/mex/segy_read_write_line_mex.c
@@ -0,0 +1,114 @@
+#include <string.h>
+#include "mex.h"
+#include "matrix.h"
+
+#include "segyutil.h"
+
+#include <segyio/segy.h>
+
+/* The gateway function */
+void mexFunction(int nlhs, mxArray *plhs[],
+                 int nrhs, const mxArray *prhs[]) {
+
+    bool read;
+
+    if (nrhs == 5) {
+        read = true;
+    }
+    else if (nrhs == 6) {
+        read = false;
+    }
+    else {
+        goto ERROR;
+    }
+
+    const mxArray* mx_spec = prhs[0];
+    const mxArray* mx_index = prhs[1];
+    const mxArray* mx_line_length = prhs[2];
+    const mxArray* mx_line_indexes = prhs[3];
+    const mxArray* mx_stride = prhs[4];
+
+    SegySpec spec;
+    recreateSpec(&spec,mx_spec);
+
+    size_t index = (size_t)mxGetScalar(mx_index);
+
+    uint32_t line_length = (uint32_t)mxGetScalar(mx_line_length);
+
+    uint32_t* line_indexes = (uint32_t*)mxGetData(mx_line_indexes);
+    int n = mxGetN(mx_line_indexes);
+    int m = mxGetM(mx_line_indexes);
+    uint32_t line_count = (n>m)? n:m;
+
+    uint32_t stride = (uint32_t)mxGetScalar(mx_stride);
+
+    FILE* fp;
+
+    unsigned int line_trace0;
+
+    int errc = segy_line_trace0( index, line_length, stride, line_indexes, line_count, &line_trace0 );
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    if (read) {
+        fp = fopen( spec.filename, "r" );
+        if (fp == NULL) {
+            goto CLEANUP;
+        }
+
+        plhs[0] = mxCreateNumericMatrix(spec.sample_count, line_length, mxSINGLE_CLASS, mxREAL);
+        float *data_ptr = (float *) mxGetData(plhs[0]);
+
+        errc = segy_read_line( fp, line_trace0, line_length, stride, data_ptr, spec.first_trace_pos, spec.trace_bsize );
+        if (errc != 0) {
+            goto CLEANUP;
+        }
+
+        errc = segy_to_native( spec.sample_format, line_length * spec.sample_count, data_ptr );
+        if (errc != 0) {
+            goto CLEANUP;
+        }
+    }
+    else {
+        fp = fopen( spec.filename, "r+" );
+        if (fp == NULL) {
+            goto CLEANUP;
+        }
+
+        const mxArray* mx_data = prhs[5];
+
+        float *data_ptr = (float *) mxGetData(mx_data);
+
+        errc = segy_from_native( spec.sample_format, line_length * spec.sample_count, data_ptr );
+        if (errc != 0) {
+            goto CLEANUP;
+        }
+
+        errc = segy_write_line( fp, line_trace0, line_length, stride, data_ptr, spec.first_trace_pos, spec.trace_bsize );
+        if (errc != 0) {
+            goto CLEANUP;
+        }
+
+        errc = segy_to_native( spec.sample_format, line_length * spec.sample_count, data_ptr );
+        if (errc != 0) {
+            goto CLEANUP;
+        }
+    }
+
+    fclose(fp);
+    return;
+
+    CLEANUP:
+    fclose(fp);
+    ERROR:
+    {
+        int nfields = 1;
+        const char *fnames[nfields];
+        fnames[0] = "error";
+        plhs[0] = mxCreateStructMatrix(0,0, nfields, fnames);
+        mxSetFieldByNumber(plhs[0], 0, 0, mxCreateDoubleScalar(errc));
+    }
+
+
+}
diff --git a/mex/segyspec_mex.c b/mex/segyspec_mex.c
new file mode 100644
index 0000000..72f3e1e
--- /dev/null
+++ b/mex/segyspec_mex.c
@@ -0,0 +1,148 @@
+#include <string.h>
+#include "mex.h"
+
+#include <../src/segyio/segy.h>
+#include "segyutil.h"
+
+mxArray *createPLHSStruct() {
+    int nfields = 11;
+    const char *fnames[nfields];
+
+    fnames[0] = "filename";
+    fnames[1] = "sample_format";
+    fnames[2] = "crossline_indexes";
+    fnames[3] = "inline_indexes";
+    fnames[4] = "sample_indexes";
+    fnames[5] = "trace_sorting_format";
+    fnames[6] = "offset_count";
+    fnames[7] = "first_trace_pos";
+    fnames[8] = "il_stride";
+    fnames[9] = "xl_stride";
+    fnames[10] = "trace_bsize";
+
+    mxArray *plhs = mxCreateStructMatrix(1,1,nfields,fnames);
+
+    return plhs;
+}
+
+
+void checkInputOutputSizes(int nlhs, int nrhs ) {
+
+    /* check for proper number of arguments */
+    if(nrhs!=4) {
+        mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nrhs","Four inputs required.");
+    }
+    if(nlhs!=1) {
+        mexErrMsgIdAndTxt("MyToolbox:arrayProduct:nlhs","One output required.");
+    }
+
+}
+
+void checkInputOutput(int nlhs, mxArray **plhs,
+                      int nrhs, const mxArray **prhs) {
+
+    checkInputOutputSizes(nlhs, nrhs);
+
+    /* First input must be a string */
+    if ( mxIsChar(prhs[0]) != 1)
+        mexErrMsgIdAndTxt( "SegyIo:segyspec:inputNotString",
+                           "Input must be a string.");
+
+    /* First input must be a row vector */
+    if (mxGetM(prhs[0])!=1)
+        mexErrMsgIdAndTxt( "SegyIo:segyspec:inputNotVector",
+                           "Input must be a row vector.");
+
+    /* make sure the second input argument is int */
+    if( !mxIsNumeric(prhs[1]) ||
+        mxGetNumberOfElements(prhs[1])!=1 ) {
+        mexErrMsgIdAndTxt("SegyIo:segyspec:notScalar","Input multiplier must be a numeric.");
+    }
+
+    /* make sure the third input argument is int */
+    if( !mxIsNumeric(prhs[2]) ||
+        mxGetNumberOfElements(prhs[2])!=1 ) {
+        mexErrMsgIdAndTxt("SegyIo:segyspec:notScalar","Input multiplier must be a int16.");
+    }
+
+    /* make sure the fourth input argument is double */
+    if( !mxIsDouble(prhs[3]) ||
+        mxGetNumberOfElements(prhs[3])!=1 ) {
+        mexErrMsgIdAndTxt("SegyIo:segyspec:notScalar","Input multiplier must be a double.");
+    }
+
+
+}
+
+/* The gateway function */
+void mexFunction( int nlhs, mxArray *plhs[],
+                  int nrhs, const mxArray *prhs[]) {
+
+    plhs[0] = createPLHSStruct();
+
+    checkInputOutput(nlhs, plhs, nrhs, prhs);
+    char *filename = mxArrayToString(prhs[0]);
+
+    int il = (int)mxGetScalar(prhs[1]);
+    int xl = (int)mxGetScalar(prhs[2]);
+    float t0 = (float)mxGetScalar(prhs[3]);
+
+    SegySpec spec;
+    int errc = segyCreateSpec(&spec, filename, il, xl, t0);
+    if (errc != 0) {
+        goto FAILURE;
+    }
+    mxSetFieldByNumber(plhs[0], 0, 0, mxCreateString(spec.filename));
+
+    mxSetFieldByNumber(plhs[0], 0, 1, mxCreateDoubleScalar(spec.sample_format));
+
+    mxArray *crossline_indexes = mxCreateDoubleMatrix(spec.crossline_count, 1, mxREAL);
+    double *crossline_indexes_ptr = mxGetPr(crossline_indexes);
+    for (int i = 0; i < spec.crossline_count; i++) {
+        crossline_indexes_ptr[i] = spec.crossline_indexes[i];
+    }
+    mxSetFieldByNumber(plhs[0], 0, 2, crossline_indexes);
+
+    mxArray *inline_indexes = mxCreateDoubleMatrix(spec.inline_count, 1, mxREAL);
+    double *inline_indexes_ptr = mxGetPr(inline_indexes);
+    for (int i = 0; i < spec.inline_count; i++) {
+        inline_indexes_ptr[i] = spec.inline_indexes[i];
+    }
+    mxSetFieldByNumber(plhs[0], 0, 3, inline_indexes);
+
+    mxArray *mx_sample_indexes = mxCreateDoubleMatrix(spec.sample_count,1, mxREAL);
+    double *mx_sample_indexes_ptr = mxGetPr(mx_sample_indexes);
+    for (int i = 0; i < spec.sample_count; i++) {
+        mx_sample_indexes_ptr[i] = spec.sample_indexes[i];
+    }
+    mxSetFieldByNumber(plhs[0], 0, 4, mx_sample_indexes);
+
+    mxSetFieldByNumber(plhs[0], 0, 5, mxCreateDoubleScalar(spec.trace_sorting_format));
+    mxSetFieldByNumber(plhs[0], 0, 6, mxCreateDoubleScalar(spec.offset_count));
+    mxSetFieldByNumber(plhs[0], 0, 7, mxCreateDoubleScalar(spec.first_trace_pos));
+    mxSetFieldByNumber(plhs[0], 0, 8, mxCreateDoubleScalar(spec.il_stride));
+    mxSetFieldByNumber(plhs[0], 0, 9, mxCreateDoubleScalar(spec.xl_stride));
+    mxSetFieldByNumber(plhs[0], 0, 10, mxCreateDoubleScalar(spec.trace_bsize));
+
+    if (spec.crossline_indexes != NULL)
+        free(spec.crossline_indexes);
+    if (spec.inline_indexes != NULL)
+        free(spec.inline_indexes);
+    if (spec.sample_indexes != NULL)
+        free(spec.sample_indexes);
+    if (spec.filename != NULL)
+        free(spec.filename);
+    mxFree(filename);
+    
+    return;
+
+    FAILURE:
+    {
+        int nfields = 1;
+        const char *fnames[nfields];
+        fnames[0] = "error";
+        plhs[0] = mxCreateStructMatrix(0,0, nfields, fnames);
+    }
+    mxFree(filename);
+
+}
diff --git a/mex/segyutil.c b/mex/segyutil.c
new file mode 100644
index 0000000..313ff5d
--- /dev/null
+++ b/mex/segyutil.c
@@ -0,0 +1,89 @@
+#include <errno.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "segyio/segy.h"
+#include "segyutil.h"
+
+static int getMaxDim(mxArray* arr){
+    int n = mxGetN(arr);
+    int m = mxGetM(arr);
+
+    int max = m;
+    if (n>m) max = n;
+
+    return max;
+}
+
+void recreateSpec(SegySpec *spec, const mxArray* mex_spec) {
+
+    spec->filename = mxArrayToString(mxGetProperty(mex_spec, 0, "filename"));
+    spec->sample_format = (unsigned int)mxGetScalar(mxGetProperty(mex_spec, 0, "sample_format"));
+    spec->trace_sorting_format = (unsigned int)mxGetScalar(mxGetProperty(mex_spec, 0, "trace_sorting_format"));
+    spec->offset_count = (unsigned int)mxGetScalar(mxGetProperty(mex_spec, 0, "offset_count"));
+    spec->first_trace_pos = (unsigned int)mxGetScalar(mxGetProperty(mex_spec, 0, "first_trace_pos"));
+    spec->il_stride = (unsigned int)mxGetScalar(mxGetProperty(mex_spec, 0, "il_stride"));
+    spec->xl_stride = (unsigned int)mxGetScalar(mxGetProperty(mex_spec, 0, "xl_stride"));
+    spec->trace_bsize = (unsigned int)mxGetScalar(mxGetProperty(mex_spec, 0, "trace_bsize"));
+
+    mxArray* crossline_indexes = mxGetProperty(mex_spec, 0, "crossline_indexes");
+    spec->crossline_count = getMaxDim(crossline_indexes);
+    spec->crossline_indexes = mxGetData(crossline_indexes);
+
+    mxArray* inline_indexes = mxGetProperty(mex_spec, 0, "inline_indexes");
+    spec->inline_count = getMaxDim(inline_indexes);
+    spec->inline_indexes = mxGetData(inline_indexes);
+
+    mxArray* sample_indexes = mxGetProperty(mex_spec, 0, "sample_indexes");
+    spec->sample_count = getMaxDim(sample_indexes);
+    spec->sample_indexes = mxGetData(sample_indexes);
+
+}
+
+struct segy_file_format buffmt( const char* binary ) {
+    struct segy_file_format fmt;
+    fmt.samples = segy_samples( binary );
+    fmt.trace_bsize = segy_trace_bsize( fmt.samples );
+    fmt.trace0 = segy_trace0( binary );
+    fmt.format = segy_format( binary );
+    fmt.traces = 0;
+
+    return fmt;
+}
+
+struct segy_file_format filefmt( FILE* fp ) {
+    char binary[SEGY_BINARY_HEADER_SIZE];
+    int err = segy_binheader( fp, binary );
+
+    if( err != 0 )
+        mexErrMsgIdAndTxt( "segy:c:filemft", strerror( errno ) );
+
+    struct segy_file_format fmt = buffmt( binary );
+
+    err = segy_traces( fp, &fmt.traces, fmt.trace0, fmt.trace_bsize );
+    if( err == 0 ) return fmt;
+
+    const char* msg1 = "segy:c:filefmt";
+    const char* msg2;
+
+    if( err == SEGY_TRACE_SIZE_MISMATCH )
+        msg2 = "Number of traces not consistent with file size. File corrupt?";
+    else
+        msg2 = strerror( errno );
+
+    mexErrMsgIdAndTxt( msg1, msg2 );
+}
+
+FILE* segyfopen( const mxArray* filename, const char* mode ) {
+    const char* fname = mxArrayToString( filename );
+
+    FILE* fp = fopen( fname, mode );
+    int err = errno;
+
+    mxFree( (void*)fname );
+
+    if( !fp )
+        mexErrMsgIdAndTxt( "segy:c:fopen", strerror( errno ) );
+
+    return fp;
+}
diff --git a/mex/segyutil.h b/mex/segyutil.h
new file mode 100644
index 0000000..2b006ab
--- /dev/null
+++ b/mex/segyutil.h
@@ -0,0 +1,26 @@
+#ifndef SEGYIO_SEGYUTIL_H
+#define SEGYIO_SEGYUTIL_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <mex.h>
+
+#include "spec/segyspec.h"
+
+
+void recreateSpec(SegySpec* spec, const mxArray* mex_spec);
+
+struct segy_file_format {
+    unsigned int samples;
+    long trace0;
+    unsigned int trace_bsize;
+    size_t traces;
+    int format;
+};
+
+struct segy_file_format buffmt( const char* );
+struct segy_file_format filefmt( FILE* );
+FILE* segyfopen( const mxArray* filename, const char* mode );
+
+#endif //SEGYIO_SEGYUTIL_H
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
new file mode 100644
index 0000000..adbf33a
--- /dev/null
+++ b/python/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(cwrap)
+add_subdirectory(segyio)
\ No newline at end of file
diff --git a/python/cwrap/CMakeLists.txt b/python/cwrap/CMakeLists.txt
new file mode 100644
index 0000000..4f70026
--- /dev/null
+++ b/python/cwrap/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(PYTHON_SOURCES
+    __init__.py
+    basecclass.py
+    basecenum.py
+    basecvalue.py
+    metacwrap.py
+    prototype.py
+)
+
+add_python_package(cwrap cwrap "${PYTHON_SOURCES}")
\ No newline at end of file
diff --git a/python/cwrap/__init__.py b/python/cwrap/__init__.py
new file mode 100644
index 0000000..788249c
--- /dev/null
+++ b/python/cwrap/__init__.py
@@ -0,0 +1,5 @@
+from .metacwrap import MetaCWrap
+from .prototype import Prototype
+from .basecclass import BaseCClass
+from .basecenum import BaseCEnum
+from .basecvalue import BaseCValue
diff --git a/python/cwrap/basecclass.py b/python/cwrap/basecclass.py
new file mode 100644
index 0000000..e2f9549
--- /dev/null
+++ b/python/cwrap/basecclass.py
@@ -0,0 +1,100 @@
+import ctypes
+from cwrap import MetaCWrap
+
+
+class BaseCClass(object):
+    __metaclass__ = MetaCWrap
+
+    def __init__(self, c_pointer, parent=None, is_reference=False):
+        if c_pointer == 0 or c_pointer is None:
+            raise ValueError("Must have a valid (not null) pointer value!")
+
+        if c_pointer < 0:
+            raise ValueError("The pointer value is negative! This may be correct, but usually is not!")
+
+        self.__c_pointer = c_pointer
+        self.__parent = parent
+        self.__is_reference = is_reference
+
+    def __new__(cls, *more, **kwargs):
+        obj = super(BaseCClass, cls).__new__(cls)
+        obj.__c_pointer = None
+        obj.__parent = None
+        obj.__is_reference = False
+
+        return obj
+
+    @classmethod
+    def from_param(cls, c_class_object):
+        if c_class_object is not None and not isinstance(c_class_object, BaseCClass):
+            raise ValueError("c_class_object must be a BaseCClass instance!")
+
+        if c_class_object is None:
+            return ctypes.c_void_p()
+        else:
+            return ctypes.c_void_p(c_class_object.__c_pointer)
+
+    @classmethod
+    def createPythonObject(cls, c_pointer):
+        if c_pointer is not None:
+            new_obj = cls.__new__(cls)
+            BaseCClass.__init__(new_obj, c_pointer=c_pointer, parent=None, is_reference=False)
+            return new_obj
+        else:
+            return None
+
+    @classmethod
+    def createCReference(cls, c_pointer, parent=None):
+        if c_pointer is not None:
+            new_obj = cls.__new__(cls)
+            BaseCClass.__init__(new_obj, c_pointer=c_pointer, parent=parent, is_reference=True)
+            return new_obj
+        else:
+            return None
+
+    @classmethod
+    def storageType(cls):
+        return ctypes.c_void_p
+
+    def convertToCReference(self, parent):
+        self.__is_reference = True
+        self.__parent = parent
+
+
+    def setParent(self, parent=None):
+        if self.__is_reference:
+            self.__parent = parent
+        else:
+            raise UserWarning("Can only set parent on reference types!")
+
+        return self
+
+    def isReference(self):
+        """ @rtype: bool """
+        return self.__is_reference
+
+    def parent(self):
+        return self.__parent
+
+    def __eq__(self, other):
+        # This is the last resort comparison function; it will do a
+        # plain pointer comparison on the underlying C object; or
+        # Python is-same-object comparison.
+        if isinstance(other, BaseCClass):
+            return self.__c_pointer == other.__c_pointer
+        else:
+            return super(BaseCClass , self).__eq__(other)
+
+
+    def free(self):
+        raise NotImplementedError("A BaseCClass requires a free method implementation!")
+
+
+
+    def __del__(self):
+        if self.free is not None:
+            if not self.__is_reference:
+                # Important to check the c_pointer; in the case of failed object creation
+                # we can have a Python object with c_pointer == None.
+                if self.__c_pointer > 0:
+                    self.free()
diff --git a/python/cwrap/basecenum.py b/python/cwrap/basecenum.py
new file mode 100644
index 0000000..29178a4
--- /dev/null
+++ b/python/cwrap/basecenum.py
@@ -0,0 +1,119 @@
+from cwrap import MetaCWrap, Prototype
+
+class BaseCEnum(object):
+    __metaclass__ = MetaCWrap
+    enum_namespace = {}
+
+    def __init__(self, *args, **kwargs):
+        if not self in self.enum_namespace[self.__class__]:
+            raise NotImplementedError("Can not be instantiated directly!")
+
+    def __new__(cls, *args, **kwargs):
+        if len(args) == 1:
+            enum = cls.__resolveEnum(args[0])
+
+            if enum is None:
+                raise ValueError("Unknown enum value: %i" % args[0])
+
+            return enum
+        else:
+            obj = super(BaseCEnum, cls).__new__(cls, *args)
+            obj.name = None
+            obj.value = None
+            return obj
+
+    @classmethod
+    def from_param(cls, c_class_object):
+        if not isinstance(c_class_object, BaseCEnum):
+            raise ValueError("c_class_object must be an BaseCEnum instance!")
+        return c_class_object.value
+
+    @classmethod
+    def addEnum(cls, name, value):
+        if not isinstance(value, int):
+            raise ValueError("Value must be an integer!")
+
+        enum = cls.__new__(cls)
+        enum.name = name
+        enum.value = value
+
+        setattr(cls, name, enum)
+
+        if not cls.enum_namespace.has_key(cls):
+            cls.enum_namespace[cls] = []
+
+        cls.enum_namespace[cls].append(enum)
+
+    @classmethod
+    def enums(cls):
+        return list(cls.enum_namespace[cls])
+
+    def __eq__(self, other):
+        if isinstance(other, self.__class__):
+            return self.value == other.value
+
+        if isinstance(other, int):
+            return self.value == other
+
+        return False
+
+    def __str__(self):
+        return self.name
+
+    def __add__(self, other):
+        self.__assertOtherIsSameType(other)
+        value = self.value + other.value
+        return self.__resolveOrCreateEnum(value)
+
+    def __or__(self, other):
+        self.__assertOtherIsSameType(other)
+        value = self.value | other.value
+        return self.__resolveOrCreateEnum(value)
+
+
+    def __xor__(self, other):
+        self.__assertOtherIsSameType(other)
+        value = self.value ^ other.value
+        return self.__resolveOrCreateEnum(value)
+
+    def __and__(self, other):
+        self.__assertOtherIsSameType(other)
+        value = self.value & other.value
+        return self.__resolveOrCreateEnum(value)
+
+    def __int__(self):
+        return self.value
+
+    def __contains__(self, item):
+        return self & item == item
+
+    @classmethod
+    def __createEnum(cls, value):
+        enum = cls.__new__(cls)
+        enum.name = "Unnamed '%s' enum with value: %i" % (str(cls.__name__), value)
+        enum.value = value
+        return enum
+
+    @classmethod
+    def __resolveOrCreateEnum(cls, value):
+        enum = cls.__resolveEnum(value)
+
+        if enum is not None:
+            return enum
+
+        return cls.__createEnum(value)
+
+    @classmethod
+    def __resolveEnum(cls, value):
+        for enum in cls.enum_namespace[cls]:
+            if enum.value == value:
+                return enum
+        return None
+
+    def __assertOtherIsSameType(self, other):
+        assert isinstance(other, self.__class__), "Can only operate on enums of same type: %s =! %s" % (
+            self.__class__.__name__, other.__class__.__name__)
+
+    @classmethod
+    def registerEnum(cls, library, enum_name):
+        Prototype.registerType(enum_name, cls)
diff --git a/python/cwrap/basecvalue.py b/python/cwrap/basecvalue.py
new file mode 100644
index 0000000..e885ac0
--- /dev/null
+++ b/python/cwrap/basecvalue.py
@@ -0,0 +1,47 @@
+from ctypes import pointer, c_long, c_int, c_bool, c_float, c_double, c_byte, c_short, c_char, c_ubyte, c_ushort, c_uint, c_ulong, c_size_t
+
+from cwrap import MetaCWrap
+
+
+class BaseCValue(object):
+    __metaclass__ = MetaCWrap
+    DATA_TYPE = None
+    LEGAL_TYPES = [c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_bool, c_char, c_float, c_double, c_size_t]
+    
+    def __init__(self, value):
+        super(BaseCValue, self).__init__()
+        
+        if not self.DATA_TYPE in self.LEGAL_TYPES:
+            raise ValueError("DATA_TYPE must be one of these CTypes classes: %s" % BaseCValue.LEGAL_TYPES)
+        
+        self.__value = self.cast(value)
+
+
+    def value(self):
+        return self.__value.value
+
+    @classmethod
+    def storageType(cls):
+        return cls.type()
+
+    @classmethod
+    def type(cls):
+        return cls.DATA_TYPE
+
+    @classmethod
+    def cast(cls, value):
+        return cls.DATA_TYPE(value)
+
+    def setValue(self, value):
+        self.__value = self.cast(value)
+
+    def asPointer(self):
+        return pointer(self.__value)
+
+    @classmethod
+    def from_param(cls, c_value_object):
+        if c_value_object is not None and not isinstance(c_value_object, BaseCValue):
+            raise ValueError("c_class_object must be a BaseCValue instance!")
+
+        return c_value_object.__value
+
diff --git a/python/cwrap/metacwrap.py b/python/cwrap/metacwrap.py
new file mode 100644
index 0000000..da31e66
--- /dev/null
+++ b/python/cwrap/metacwrap.py
@@ -0,0 +1,46 @@
+import re
+from types import MethodType
+
+from cwrap.prototype import registerType, Prototype
+
+
+def snakeCase(name):
+    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
+
+
+class MetaCWrap(type):
+    def __init__(cls, name, bases, attrs):
+        super(MetaCWrap, cls).__init__(name, bases, attrs)
+
+        is_return_type = False
+        storage_type = None
+
+        if "TYPE_NAME" in attrs:
+            type_name = attrs["TYPE_NAME"]
+        else:
+            type_name = snakeCase(name)
+
+        if hasattr(cls, "DATA_TYPE") or hasattr(cls, "enums"):
+            is_return_type = True
+
+        if hasattr(cls, "storageType"):
+            storage_type = cls.storageType()
+
+        registerType(type_name, cls, is_return_type=is_return_type, storage_type=storage_type)
+
+        if hasattr(cls, "createCReference"):
+            registerType("%s_ref" % type_name, cls.createCReference, is_return_type=True, storage_type=storage_type)
+
+        if hasattr(cls, "createPythonObject"):
+            registerType("%s_obj" % type_name, cls.createPythonObject, is_return_type=True, storage_type=storage_type)
+
+
+        for key, attr in attrs.items():
+            if isinstance(attr, Prototype):
+                attr.resolve()
+                attr.__name__ = key
+
+                if attr.shouldBeBound():
+                    method = MethodType(attr, None, cls)
+                    setattr(cls, key, method)
diff --git a/python/cwrap/prototype.py b/python/cwrap/prototype.py
new file mode 100644
index 0000000..34aaf82
--- /dev/null
+++ b/python/cwrap/prototype.py
@@ -0,0 +1,147 @@
+import ctypes
+import inspect
+import re
+
+import sys
+
+class TypeDefinition(object):
+    def __init__(self, type_class_or_function, is_return_type, storage_type):
+        self.storage_type = storage_type
+        self.is_return_type = is_return_type
+        self.type_class_or_function = type_class_or_function
+
+
+REGISTERED_TYPES = {}
+""":type: dict[str,TypeDefinition]"""
+
+
+def registerType(type_name, type_class_or_function, is_return_type=True, storage_type=None):
+    if type_name in REGISTERED_TYPES:
+        raise PrototypeError("Type: '%s' already registered!" % type_name)
+
+    REGISTERED_TYPES[type_name] = TypeDefinition(type_class_or_function, is_return_type, storage_type)
+
+    # print("Registered: %s for class: %s" % (type_name, repr(type_class_or_function)))
+
+registerType("void", None)
+registerType("void*", ctypes.c_void_p)
+registerType("uint", ctypes.c_uint)
+registerType("uint*", ctypes.POINTER(ctypes.c_uint))
+registerType("int", ctypes.c_int)
+registerType("int*", ctypes.POINTER(ctypes.c_int))
+registerType("int64", ctypes.c_int64)
+registerType("int64*", ctypes.POINTER(ctypes.c_int64))
+registerType("size_t", ctypes.c_size_t)
+registerType("size_t*", ctypes.POINTER(ctypes.c_size_t))
+registerType("bool", ctypes.c_bool)
+registerType("bool*", ctypes.POINTER(ctypes.c_bool))
+registerType("long", ctypes.c_long)
+registerType("long*", ctypes.POINTER(ctypes.c_long))
+registerType("char", ctypes.c_char)
+registerType("char*", ctypes.c_char_p)
+registerType("char**", ctypes.POINTER(ctypes.c_char_p))
+registerType("float", ctypes.c_float)
+registerType("float*", ctypes.POINTER(ctypes.c_float))
+registerType("double", ctypes.c_double)
+registerType("double*", ctypes.POINTER(ctypes.c_double))
+registerType("py_object", ctypes.py_object)
+
+PROTOTYPE_PATTERN = "(?P<return>[a-zA-Z][a-zA-Z0-9_*]*) +(?P<function>[a-zA-Z]\w*) *[(](?P<arguments>[a-zA-Z0-9_*, ]*)[)]"
+
+class PrototypeError(Exception):
+    pass
+
+
+class Prototype(object):
+    pattern = re.compile(PROTOTYPE_PATTERN)
+
+    def __init__(self, lib, prototype, bind=False):
+        super(Prototype, self).__init__()
+        self._lib = lib
+        self._prototype = prototype
+        self._bind = bind
+        self._func = None
+        self.__name__ = prototype
+        self._resolved = False
+
+
+    def _parseType(self, type_name):
+        """Convert a prototype definition type from string to a ctypes legal type."""
+        type_name = type_name.strip()
+
+        if type_name in REGISTERED_TYPES:
+            type_definition = REGISTERED_TYPES[type_name]
+            return type_definition.type_class_or_function, type_definition.storage_type
+        raise ValueError("Unknown type: %s" % type_name)
+
+
+    def shouldBeBound(self):
+        return self._bind
+
+    def resolve(self):
+        match = re.match(Prototype.pattern, self._prototype)
+        if not match:
+            raise PrototypeError("Illegal prototype definition: %s\n" % self._prototype)
+        else:
+            restype = match.groupdict()["return"]
+            function_name = match.groupdict()["function"]
+            self.__name__ = function_name
+            arguments = match.groupdict()["arguments"].split(",")
+
+            try:
+                func = getattr(self._lib, function_name)
+            except AttributeError:
+                raise PrototypeError("Can not find function: %s in library: %s" % (function_name , self._lib))
+
+            if not restype in REGISTERED_TYPES or not REGISTERED_TYPES[restype].is_return_type:
+                sys.stderr.write("The type used as return type: %s is not registered as a return type.\n" % restype)
+
+                return_type = self._parseType(restype)
+
+                if inspect.isclass(return_type):
+                    sys.stderr.write("  Correct type may be: %s_ref or %s_obj.\n" % (restype, restype))
+
+                return None
+
+            return_type, storage_type = self._parseType(restype)
+
+            func.restype = return_type
+
+            if storage_type is not None:
+                func.restype = storage_type
+
+                def returnFunction(result, func, arguments):
+                    return return_type(result)
+
+                func.errcheck = returnFunction
+
+            if len(arguments) == 1 and arguments[0].strip() == "":
+                func.argtypes = []
+            else:
+                argtypes = [self._parseType(arg)[0] for arg in arguments]
+                if len(argtypes) == 1 and argtypes[0] is None:
+                    argtypes = []
+                func.argtypes = argtypes
+
+            self._func = func
+
+
+    def __call__(self, *args):
+        if not self._resolved:
+            self.resolve()
+            self._resolved = True
+
+        if self._func is None:
+            raise PrototypeError("Prototype has not been properly resolved!")
+        return self._func(*args)
+
+    def __repr__(self):
+        bound = ""
+        if self.shouldBeBound():
+            bound = ", bind=True"
+
+        return 'Prototype("%s"%s)' % (self._prototype, bound)
+
+    @classmethod
+    def registerType(cls, type_name, type_class_or_function, is_return_type=True, storage_type=None):
+        registerType(type_name, type_class_or_function, is_return_type=is_return_type, storage_type=storage_type)
diff --git a/python/segyio/CMakeLists.txt b/python/segyio/CMakeLists.txt
new file mode 100644
index 0000000..7345f1a
--- /dev/null
+++ b/python/segyio/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(PYTHON_SOURCES
+    __init__.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/__init__.py b/python/segyio/__init__.py
new file mode 100644
index 0000000..b2996f0
--- /dev/null
+++ b/python/segyio/__init__.py
@@ -0,0 +1,42 @@
+"""
+simple segy input/output
+
+Welcome to segyio. For help, examples and reference, type `help(function)` in
+your favourite python interpreter, or `pydoc function` in the unix console.
+
+The segy library attempts to be easy to use efficently for prototyping and
+interaction with possibly large segy files. File reading and writing is
+streaming, with large file support out of the box and without hassle. For a
+quick start on reading files, type `help(segyio.open)`.
+
+An open segy file is interacted with in modes, found in the segy module. For a
+reference with examples, please type `help(segyio.segy)`. For documentation on
+individual modes, please refer to the individual modes with
+`help(segyio.segy.file.[mode])`, or look it up in the aggregated segyio.segy.
+The available modes are:
+    * text, for textual headers including extended headers
+    * bin, for the binary header
+    * header, for the trace headers
+    * trace, for trace data
+    * iline, for inline biased operations
+    * xline, for crossline biased operations
+
+The primary data type is the numpy array. All examples use `np` for the numpy
+namespace. That means that any function that returns a trace, a set of samples
+or even full lines, returns a numpy array. This enables quick and easy
+mathematical operations on the data you care about.
+
+Segyio is designed to blend into regular python code, so python concepts that
+map to segy operations are written to behave similarly. That means that
+sequences of data support list lookup, slicing (`f.trace[0:10:2]`), `for x in`
+etc. Please refer to the individual mode's documentation for a more exhaustive
+list with examples.
+"""
+
+from .segysampleformat import SegySampleFormat
+from .tracesortingformat import TraceSortingFormat
+from .tracefield import TraceField
+from .binfield import BinField
+from .open import open
+from .create import create
+from .segy import file, spec
diff --git a/python/segyio/binfield.py b/python/segyio/binfield.py
new file mode 100644
index 0000000..f1dd91e
--- /dev/null
+++ b/python/segyio/binfield.py
@@ -0,0 +1,70 @@
+from cwrap import BaseCEnum
+
+class BinField(BaseCEnum):
+    TYPE_NAME = "SEGY_BINFIELD"
+
+    JobID = None
+    LineNumber = None
+    ReelNumber = None
+    Traces = None
+    AuxTraces = None
+    Interval = None
+    IntervalOriginal = None
+    Samples = None
+    SamplesOriginal = None
+    Format = None
+    EnsembleFold = None
+    SortingCode = None
+    VerticalSum = None
+    SweepFrequencyStart = None
+    SweepFrequencyEnd = None
+    SweepLength = None
+    Sweep = None
+    SweepChannel = None
+    SweepTaperStart = None
+    SweepTaperEnd = None
+    Taper = None
+    CorrelatedTraces = None
+    BinaryGainRecovery = None
+    AmplitudeRecovery = None
+    MeasurementSystem = None
+    ImpulseSignalPolarity = None
+    VibratoryPolarity = None
+    Unassigned1 = None
+    SEGYRevision = None
+    TraceFlag = None
+    ExtendedHeaders = None
+    Unassigned2 = None
+
+BinField.addEnum("JobID", 3201)
+BinField.addEnum("LineNumber", 3205)
+BinField.addEnum("ReelNumber", 3209)
+BinField.addEnum("Traces", 3213)
+BinField.addEnum("AuxTraces", 3215)
+BinField.addEnum("Interval", 3217)
+BinField.addEnum("IntervalOriginal", 3219)
+BinField.addEnum("Samples", 3221)
+BinField.addEnum("SamplesOriginal", 3223)
+BinField.addEnum("Format", 3225)
+BinField.addEnum("EnsembleFold", 3227)
+BinField.addEnum("SortingCode", 3229)
+BinField.addEnum("VerticalSum", 3231)
+BinField.addEnum("SweepFrequencyStart", 3233)
+BinField.addEnum("SweepFrequencyEnd", 3235)
+BinField.addEnum("SweepLength", 3237)
+BinField.addEnum("Sweep", 3239)
+BinField.addEnum("SweepChannel", 3241)
+BinField.addEnum("SweepTaperStart", 3243)
+BinField.addEnum("SweepTaperEnd", 3245)
+BinField.addEnum("Taper", 3247)
+BinField.addEnum("CorrelatedTraces", 3249)
+BinField.addEnum("BinaryGainRecovery", 3251)
+BinField.addEnum("AmplitudeRecovery", 3253)
+BinField.addEnum("MeasurementSystem", 3255)
+BinField.addEnum("ImpulseSignalPolarity", 3257)
+BinField.addEnum("VibratoryPolarity", 3259)
+BinField.addEnum("Unassigned1", 3261)
+BinField.addEnum("SEGYRevision", 3501)
+BinField.addEnum("TraceFlag", 3503)
+BinField.addEnum("ExtendedHeaders", 3505)
+BinField.addEnum("Unassigned2", 3507)
diff --git a/python/segyio/create.py b/python/segyio/create.py
new file mode 100644
index 0000000..d0c64d0
--- /dev/null
+++ b/python/segyio/create.py
@@ -0,0 +1,135 @@
+import datetime
+import ctypes as ct
+import segyio
+
+def default_text_header(iline, xline, offset):
+    return ''.join([
+    "C 1 DATE: %s                                                            ",
+    "C 2 AN INCREASE IN AMPLITUDE EQUALS AN INCREASE IN ACOUSTIC IMPEDANCE           ",
+    "C 3 Written by libsegyio (python)                                               ",
+    "C 4                                                                             ",
+    "C 5                                                                             ",
+    "C 6                                                                             ",
+    "C 7                                                                             ",
+    "C 8                                                                             ",
+    "C 9                                                                             ",
+    "C10                                                                             ",
+    "C11 TRACE HEADER POSITION:                                                      ",
+    "C12   INLINE BYTES %03d-%03d    | OFFSET BYTES %03d-%03d                            ",
+    "C13   CROSSLINE BYTES %03d-%03d |                                                 ",
+    "C14                                                                             ",
+    "C15 END EBCDIC HEADER                                                           ",
+    "C16                                                                             ",
+    "C17                                                                             ",
+    "C18                                                                             ",
+    "C19                                                                             ",
+    "C20                                                                             ",
+    "C21                                                                             ",
+    "C22                                                                             ",
+    "C23                                                                             ",
+    "C24                                                                             ",
+    "C25                                                                             ",
+    "C26                                                                             ",
+    "C27                                                                             ",
+    "C28                                                                             ",
+    "C29                                                                             ",
+    "C30                                                                             ",
+    "C31                                                                             ",
+    "C32                                                                             ",
+    "C33                                                                             ",
+    "C34                                                                             ",
+    "C35                                                                             ",
+    "C36                                                                             ",
+    "C37                                                                             ",
+    "C38                                                                             ",
+    "C39                                                                             ",
+    "C40                                                                            \x80"]) \
+    % (datetime.date.today(), iline, iline + 4, int(offset), int(offset) + 4, xline, xline + 4)
+
+def create(filename, spec):
+    """Create a new segy file.
+
+    Create a new segy file with the geometry and properties given by `spec`.
+    This enables creating SEGY files from your data. The created file supports
+    all segyio modes, but has an emphasis on writing. The spec must be
+    complete, otherwise an exception will be raised. A default, empty spec can
+    be created with `segyio.spec()`.
+
+    Very little data is written to the file, so just calling create is not
+    sufficient to re-read the file with segyio. Rather, every trace header and
+    trace must be written to for the file to be considered complete.
+
+    Create should be used together with python's `with` statement. This ensure
+    the data is written. Please refer to the examples.
+
+    Args:
+        filename (str): Path to file to open.
+        spec (:obj: `spec`): Structure of the segy file.
+
+    Examples:
+        Create a file::
+            >>> spec = segyio.spec()
+            >>> spec.ilines  = [1, 2, 3, 4]
+            >>> spec.xlines  = [11, 12, 13]
+            >>> spec.samples = 50
+            >>> spec.sorting = 2
+            >>> spec.format  = 1
+            >>> with segyio.create(path, spec) as f:
+            ...     ## fill the file with data
+            ...
+
+        Copy a file, but shorten all traces by 50 samples::
+            >>> with segyio.open(srcpath) as src:
+            ...     spec = segyio.spec()
+            ...     spec.sorting = src.sorting
+            ...     spec.format = src.format
+            ...     spec.samples = src.samples - 50
+            ...     spec.ilines = src.ilines
+            ...     spec.xline = src.xlines
+            ...     with segyio.create(dstpath, spec) as dst:
+            ...         dst.text[0] = src.text[0]
+            ...         dst.bin = src.bin
+            ...         dst.header = src.header
+            ...         dst.trace = src.trace
+    """
+    f = segyio.file(filename, "w+")
+
+    f.samples       = spec.samples
+    f.ext_headers   = spec.ext_headers
+    f._bsz          = segyio.file._trace_bsize(f.samples)
+    f._tr0          = -1 + segyio.file._textsize() + \
+                      segyio.file._binheader_size() + \
+                      (spec.ext_headers * segyio.file._textsize())
+    f.sorting       = spec.sorting
+    f._fmt          = spec.format
+    f.offsets       = spec.offsets
+    f.tracecount    = len(spec.ilines) * len(spec.xlines) * spec.offsets
+
+    f._il           = int(spec.iline)
+    f.ilines        = spec.ilines
+    f._raw_ilines   = (ct.c_uint * len(f.ilines))()
+    for i, x in enumerate(f.ilines):
+        f._raw_ilines[i] = x
+
+    f._xl           = int(spec.xline)
+    f.xlines        = spec.xlines
+    f._raw_xlines   = (ct.c_uint * len(f.xlines))()
+    for i, x in enumerate(f.xlines):
+        f._raw_xlines[i] = x
+
+
+    f._iline_length = f._init_iline_length(len(f.xlines))
+    f._iline_stride = f._init_iline_stride(len(f.ilines))
+
+    f._xline_length = f._init_xline_length(len(f.ilines))
+    f._xline_stride = f._init_xline_stride(len(f.xlines))
+
+    f.text[0] = default_text_header(f._il, f._xl, segyio.TraceField.offset)
+    f.bin     = {   3213: f.tracecount,
+                    3217: 4000,
+                    3221: f.samples,
+                    3225: f.format,
+                    3505: f.ext_headers,
+                }
+
+    return f
diff --git a/python/segyio/open.py b/python/segyio/open.py
new file mode 100644
index 0000000..f9b1580
--- /dev/null
+++ b/python/segyio/open.py
@@ -0,0 +1,74 @@
+import segyio
+
+def open(filename, mode = "r", iline = 189, xline = 193):
+    """Open a segy file.
+
+    Opens a segy file and tries to figure out its sorting, inline numbers,
+    crossline numbers, and offsets, and enables reading and writing to this
+    file in a simple manner.
+
+    For reading, the access mode "r" is preferred. All write operations will
+    raise an exception. For writing, the mode "r+" is preferred (as "rw" would
+    truncate the file). The modes used are standard C file modes; please refer
+    to that documentation for a complete reference.
+
+    Open should be used together with python's `with` statement. Please refer
+    to the examples. When the `with` statement is used the file will
+    automatically be closed when the routine completes or an exception is
+    raised.
+
+    Args:
+        filename (str): Path to file to open.
+        mode (str, optional): File access mode, defaults to "r".
+        iline (TraceField): Inline number field in the trace headers. Defaults
+                            to 189 as per the SEGY specification.
+        xline (TraceField): Crossline number field in the trace headers.
+                            Defaults to 193 as per the SEGY specification.
+
+    Examples:
+        Open a file in read-only mode::
+            >>> with segyio.open(path, "r") as f:
+            ...     print(f.ilines)
+            ...
+
+        Open a file in read-write mode::
+            >>> with segyio.open(path, "r+") as f:
+            ...     f.trace = np.arange(100)
+            ...
+
+        Open two files at once::
+            >>> with segyio.open(path) as src, segyio.open(path, "r+") as dst:
+            ...     dst.trace = src.trace # copy all traces from src to dst
+            ...
+    """
+    f = segyio.file(filename, mode, iline, xline)
+
+    try:
+        header = f.bin.buf
+
+        f.samples = f._get_samples(header)
+        f._tr0 = f._trace0(header)
+        f._fmt = f._format(header)
+        f._bsz = f._trace_bsize(f.samples)
+        f.ext_headers   = (f._tr0 - 3600) / 3200 # should probably be from C
+
+        f.tracecount = f._init_traces()
+        f.sorting    = f._init_sorting()
+        f.offsets    = f._init_offsets()
+
+        iline_count, xline_count = f._init_line_count()
+
+        f.ilines, f._raw_ilines = f._init_ilines(iline_count, xline_count)
+        f._iline_length = f._init_iline_length(xline_count)
+        f._iline_stride = f._init_iline_stride(iline_count)
+
+        f.xlines, f._raw_xlines = f._init_xlines(iline_count, xline_count)
+        f._xline_length = f._init_xline_length(iline_count)
+        f._xline_stride = f._init_xline_stride(xline_count)
+
+    except:
+        f.close()
+        raise
+
+    return f
+
diff --git a/python/segyio/segy.py b/python/segyio/segy.py
new file mode 100644
index 0000000..1ef6c59
--- /dev/null
+++ b/python/segyio/segy.py
@@ -0,0 +1,1302 @@
+"""
+segy modes
+
+Welcome to segyio.segy. Here you will find references and examples for the
+various segy modes and how to interact with segy files. To start interacting
+with files, please refer to the segyio.open and segyio.create documentation, by
+typing `help(segyio.open)` or `help(segyio.create)`.
+
+The primary way of obtaining a file instance is calling segyio.open. When you
+have a file instance you can interact with it as described in this module.
+
+The explanations and examples here are meant as a quick guide and reference.
+You can also have a look at the example programs that are distributed with
+segyio which you can find in the examples directory or where your distribution
+installs example programs.
+"""
+
+import sys, os, errno
+import itertools
+import numpy as np
+import ctypes as ct
+from cwrap import BaseCClass, BaseCEnum, Prototype
+from segyio import TraceField, BinField
+
+class _Segyio(Prototype):
+    SEGYIO_LIB = ct.CDLL("libsegyio.so", use_errno=True)
+
+    def __init__(self, prototype, bind=True):
+        super(_Segyio, self).__init__(_Segyio.SEGYIO_LIB, prototype, bind=bind)
+
+def _floatp(obj):
+    return obj.ctypes.data_as(ct.POINTER(ct.c_float))
+
+# match the SEGY_ERROR enum from segy.h
+class _error(BaseCEnum):
+    TYPE_NAME = "SEGY_ERROR"
+
+    OK = None
+    FOPEN_ERROR = None
+    FSEEK_ERROR = None
+    FREAD_ERROR = None
+    FWRITE_ERROR = None
+    INVALID_FIELD = None
+    INVALID_SORTING = None
+    MISSING_LINE_INDEX = None
+    INVALID_OFFSETS = None
+    TRACE_SIZE_MISMATCH = None
+
+_error.addEnum("OK", 0)
+_error.addEnum("FOPEN_ERROR", 1)
+_error.addEnum("FSEEK_ERROR", 2)
+_error.addEnum("FREAD_ERROR", 3)
+_error.addEnum("FWRITE_ERROR", 4)
+_error.addEnum("INVALID_FIELD", 5)
+_error.addEnum("INVALID_SORTING", 6)
+_error.addEnum("MISSING_LINE_INDEX", 7)
+_error.addEnum("INVALID_OFFSETS", 8)
+_error.addEnum("TRACE_SIZE_MISMATCH", 9)
+
+def _header_buffer(buf = None):
+    if buf is None:
+        return (ct.c_char * 240)()
+
+    if len(buf) < 240:
+        raise ValueError("Buffer must be a minimum %d size'd byte array." % 240)
+
+    return buf
+
+def _set_field(buf, field, x):
+    errc = file._set_field(buf, int(field), x)
+    err = _error(errc)
+
+    if err != _error.OK:
+        raise IndexError("Invalid byte offset %d" % field)
+
+def _get_field(buf, field, x):
+    errc = file._get_field(buf, int(field), ct.byref(x))
+    err = _error(errc)
+
+    if err != _error.OK:
+        raise IndexError("Invalid byte offset %d" % field)
+
+    return int(x.value)
+
+def _write_header(buf, segy, traceno):
+    errc = segy._write_header(traceno, buf, segy._tr0, segy._bsz)
+    err = _error(errc)
+
+    if err != _error.OK:
+        errno = ct.get_errno()
+        raise OSError(errno, "Error writing header for trace %d: %s" % (traceno, os.strerror(errno)))
+
+
+class _line:
+    """ Line mode for traces and trace headers. Internal.
+
+    The _line class provides an interface for line-oriented operations. The
+    line reading operations themselves are not streaming - it's assumed than
+    when a line is queried it's somewhat limited in size and will comfortably
+    fit in memory, and that the full line is interesting. This also applies to
+    line headers; however, all returned values support the iterable protocol so
+    they work fine together with the streaming bits of this library.
+
+    _line should not be instantiated directly by users, but rather returned
+    from the iline/xline properties of file or from the header mode. Any
+    direct construction of this should be conisdered an error.
+    """
+    def __init__(self, segy, length, stride, lines, raw_lines, other_lines, buffn, readfn, writefn, name):
+        self.segy = segy
+        self.len = length
+        self.stride = stride
+        self.lines = lines
+        self.raw_lines = raw_lines
+        self.other_lines = other_lines
+        self.name = name
+        self.buffn = buffn
+        self.readfn = readfn
+        self.writefn = writefn
+
+    def __getitem__(self, lineno, buf = None):
+        if isinstance(lineno, tuple):
+            return self.__getitem__(lineno[0], lineno[1])
+
+        buf = self.buffn(buf)
+
+        if isinstance(lineno, int):
+            t0 = self.segy._fread_trace0(lineno, len(self.other_lines), self.stride, self.raw_lines, self.name)
+            return self.readfn(t0, self.len, self.stride, buf)
+
+        elif isinstance(lineno, slice):
+            # in order to support [:end] syntax, we must make sure
+            # start has a non-None value. lineno.indices() would set it
+            # to 0, but we don't know if that's a reasonable value or
+            # not. If start is None we set it to the first line
+            if lineno.start is None:
+                lineno = slice(self.lines[0], lineno.stop, lineno.step)
+
+            def gen():
+                s = set(self.lines)
+                rng = xrange(*lineno.indices(self.lines[-1] + 1))
+
+                # use __getitem__ lookup to avoid tuple
+                # construction and unpacking and fast-forward
+                # into the interesting code path
+                for i in itertools.ifilter(s.__contains__, rng):
+                    yield self.__getitem__(i, buf)
+
+            return gen()
+
+    def __setitem__(self, lineno, val):
+        if isinstance(lineno, slice):
+            if lineno.start is None:
+                lineno = slice(self.lines[0], lineno.stop, lineno.step)
+
+            rng = xrange(*lineno.indices(self.lines[-1] + 1))
+            s = set(self.lines)
+
+            for i, x in itertools.izip(filter(s.__contains__, rng), val):
+                self.__setitem__(i, x)
+
+            return
+
+        t0 = self.segy._fread_trace0(lineno, len(self.other_lines), self.stride, self.raw_lines, self.name)
+        self.writefn(t0, self.len, self.stride, val)
+
+    def __len__(self):
+        return len(self.lines)
+
+    def __iter__(self):
+        buf = self.buffn()
+        for i in self.lines:
+            yield self.__getitem__(i, buf)
+
+class _header:
+    def __init__(self, segy):
+        self.segy = segy
+
+    class proxy:
+        def __init__(inner, buf, traceno = None, segy = None, get_field = _get_field, set_field = _set_field, write = _write_header, field_type = TraceField):
+            inner.buf = buf
+            inner.traceno = traceno
+            inner._segy = segy
+            inner._get_field = get_field
+            inner._set_field = set_field
+            inner._field_type = field_type
+            inner._write = write
+
+        def __getitem__(inner, field):
+            val = ct.c_int()
+
+            # add some structure so we can always iterate over fields
+            if isinstance(field, int) or isinstance(field, inner._field_type):
+                field = [field]
+
+            d = { inner._field_type(f): inner._get_field(inner.buf, f, val) for f in field }
+
+            # unpack the dictionary. if header[field] is requested, a
+            # plain, unstructed output is expected, but header[f1,f2,f3]
+            # yields a dict
+            if len(d) == 1:
+                return d.values()[0]
+
+            return d
+
+        def __setitem__(inner, field, val):
+            inner._set_field(inner.buf, field, val)
+            inner._write(inner.buf, inner._segy, inner.traceno)
+
+    def __getitem__(self, traceno, buf = None):
+        if isinstance(traceno, tuple):
+            return self.__getitem__(traceno[0], traceno[1])
+
+        buf = _header_buffer(buf)
+
+        if isinstance(traceno, slice):
+            def gen():
+                for i in xrange(*traceno.indices(self.segy.tracecount)):
+                    yield self.__getitem__(i, buf)
+
+            return gen()
+
+        buf = self.segy._readh(traceno, buf)
+        return _header.proxy(buf, traceno = traceno, segy = self.segy)
+
+    def __setitem__(self, traceno, val):
+        buf = None
+
+        # library-provided loops can re-use a buffer for the lookup, even in
+        # __setitem__, so we might need to unpack the tuple to reuse the buffer
+        if isinstance(traceno, tuple):
+            buf = traceno[1]
+            traceno = traceno[0]
+
+        if isinstance(val, dict):
+            try:
+                # try to read a buffer. If the file was created by
+                # libsegyio this might not have been written to before and
+                # getitem might fail, so we try to read it and if it fails
+                # we check if we're still within bounds if we are we create
+                # an empty header and write to that
+                buf = self.__getitem__(traceno, buf).buf
+
+            except:
+                if traceno >= self.segy.tracecount: raise
+                buf = _header_buffer(buf)
+
+            for f, v in val.items():
+                _set_field(buf, f, v)
+
+        else:
+            buf = val.buf
+
+        _write_header(buf, self.segy, traceno)
+
+    def __iter__(self):
+        return self[:]
+
+    def readfn(self, t0, length, stride, buf):
+        def gen():
+            start = t0
+            stop  = t0 + (length * stride)
+            for i in xrange(start, stop, stride):
+                self.segy._readh(i, buf)
+                yield _header.proxy(buf, traceno = i, segy = self.segy)
+
+        return gen()
+
+    def writefn(self, t0, length, stride, val):
+        start = t0
+        stop  = t0 + (length * stride)
+
+        if isinstance(val, _header.proxy) or isinstance(val, dict):
+            val = itertools.repeat(val)
+
+        for i, x in itertools.izip(xrange(start, stop, stride), val):
+            self[i] = x
+
+
+    @property
+    def iline(self):
+        segy = self.segy
+        length = segy._iline_length
+        stride = segy._iline_stride
+        lines  = segy.ilines
+        raw_lines = segy._raw_ilines
+        other_lines  = segy.xlines
+        buffn = _header_buffer
+
+        return _line(segy, length, stride, lines, raw_lines, other_lines, buffn, self.readfn, self.writefn, "Inline")
+
+    @property
+    def xline(self):
+        segy = self.segy
+        length = segy._xline_length
+        stride = segy._xline_stride
+        lines  = segy.xlines
+        raw_lines = segy._raw_xlines
+        other_lines  = segy.xlines
+        buffn = _header_buffer
+
+        return _line(segy, length, stride, lines, raw_lines, other_lines, buffn, self.readfn, self.writefn, "Crossline")
+
+    def __setattr__(self, name, value):
+        """Write iterables to lines
+
+        Examples:
+            setattr supports writing to *all* inlines and crosslines via
+            assignment, regardless of data source and format. Will respect the
+            sample size and structure of the file being assigned to, so if the
+            argument traces are longer than that of the file being written to
+            the surplus data will be ignored. Uses same rules for writing as
+            `f.iline[i] = x`.
+        """
+        if name == "iline":
+            for i, src in itertools.izip(self.segy.ilines, value):
+                self.iline[i] = src
+
+        if name == "xline":
+            for i, src in itertools.izip(self.segy.xlines, value):
+                self.xline[i] = src
+
+        else:
+            self.__dict__[name] = value
+            return
+
+class file(BaseCClass):
+    TYPE_NAME = "FILE"
+
+    _open  = _Segyio("void* fopen(char*, char*)", bind = False)
+    _flush = _Segyio("int fflush(FILE)")
+    _close = _Segyio("int fclose(FILE)")
+
+    _binheader_size     = _Segyio("uint segy_binheader_size()", bind = False)
+    _binheader          = _Segyio("int segy_binheader(FILE, char*)")
+    _write_binheader    = _Segyio("int segy_write_binheader(FILE, char*)")
+
+    _trace0         = _Segyio("int  segy_trace0(char*)", bind = False)
+    _get_samples    = _Segyio("uint segy_samples(char*)", bind = False)
+    _format         = _Segyio("int  segy_format(char*)", bind = False)
+    _sorting        = _Segyio("int  segy_sorting(FILE, int, int, int*, long, uint)")
+    _trace_bsize    = _Segyio("uint segy_trace_bsize(uint)", bind = False)
+    _traces         = _Segyio("uint segy_traces(FILE, uint*, long, uint)")
+    _offsets        = _Segyio("uint segy_offsets(FILE, int, int, uint, uint*, long, uint)")
+
+    _get_field      = _Segyio("int segy_get_field(char*, int, int*)", bind = False)
+    _set_field      = _Segyio("int segy_set_field(char*, int, int)", bind = False)
+
+    _get_bfield   = _Segyio("int segy_get_bfield(char*, int, int*)", bind = False)
+    _set_bfield   = _Segyio("int segy_set_bfield(char*, int, int)", bind = False)
+
+    _to_native      = _Segyio("int segy_to_native(int, uint, float*)", bind = False)
+    _from_native    = _Segyio("int segy_from_native(int, uint, float*)", bind = False)
+
+    _read_header    = _Segyio("int segy_traceheader(FILE, uint, char*, long, uint)")
+    _write_header   = _Segyio("int segy_write_traceheader(FILE, uint, char*, long, uint)")
+    _read_trace     = _Segyio("int segy_readtrace(FILE, uint, float*, long, uint)")
+    _write_trace    = _Segyio("int segy_writetrace(FILE, uint, float*, long, uint)")
+
+    _count_lines        = _Segyio("int segy_count_lines(FILE, int, uint, uint*, uint*, long, uint)")
+    _inline_length      = _Segyio("int segy_inline_length(int, uint, uint, uint, uint*)", bind = False)
+    _inline_stride      = _Segyio("int segy_inline_stride(int, uint, uint*)", bind = False)
+    _inline_indices     = _Segyio("int segy_inline_indices(FILE, int, int, uint, uint, uint, uint*, long, uint)")
+    _crossline_length   = _Segyio("int segy_crossline_length(int, uint, uint, uint, uint*)", bind = False)
+    _crossline_stride   = _Segyio("int segy_crossline_stride(int, uint, uint*)", bind = False)
+    _crossline_indices  = _Segyio("int segy_crossline_indices(FILE, int, int, uint, uint, uint, uint*, long, uint)")
+    _line_trace0        = _Segyio("int segy_line_trace0( uint, uint, uint, uint*, uint, uint*)", bind = False)
+    _read_line          = _Segyio("int segy_read_line(FILE, uint, uint, uint, float*, long, uint)")
+    _write_line         = _Segyio("int segy_write_line(FILE, uint, uint, uint, float*, long, uint)")
+
+    _textsize = _Segyio("int segy_textheader_size()", bind = False)
+    _texthdr  = _Segyio("int segy_textheader(FILE, char*)")
+    _write_texthdr  = _Segyio("int segy_write_textheader(FILE, uint, char*)")
+
+    def __init__(self, filename, mode, iline = 189 , xline = 193, t0 = 1111.0 ):
+        """
+        Constructor, internal.
+        """
+        self._filename  = filename
+        self._mode      = mode
+        self._il        = iline
+        self._xl        = xline
+        fd              = self._open(filename, mode)
+
+        if not fd:
+            errno = ct.get_errno()
+            strerror = os.strerror(errno)
+            raise OSError(errno, "Opening file '%s' failed: %s" % (filename, strerror))
+
+        super(file, self).__init__(fd)
+
+    def _init_traces(self):
+        traces = ct.c_uint()
+        errc = self._traces(ct.byref(traces), self._tr0, self._bsz)
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(traces.value)
+
+        if err == _error.TRACE_SIZE_MISMATCH:
+            raise RuntimeError("Number of traces is not consistent with file size. File probably corrupt.")
+
+        errno = ct.get_errno()
+        raise OSError("Error while detecting number of traces: %s" % os.strerror(errno))
+
+    def _init_sorting(self):
+        sorting = ct.c_int()
+        errc = self._sorting( self._il, self._xl, ct.byref(sorting), self._tr0, self._bsz)
+
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(sorting.value)
+
+        if err == _error.INVALID_FIELD:
+            raise ValueError("Invalid inline (%d) or crossline (%d) field/byte offset. "\
+                             "Too large or between valid byte offsets" % (self._il, self._xl))
+
+        if err == _error.INVALID_SORTING:
+            raise RuntimeError("Unable to determine sorting. File probably corrupt.")
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Error while detecting file sorting: %s" % os.strerror(errno))
+
+        return int(sorting.value)
+
+    def _init_offsets(self):
+        offsets = ct.c_uint()
+        errc = self._offsets(self._il, self._xl, self.tracecount, ct.byref(offsets), self._tr0, self._bsz)
+
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(offsets.value)
+
+        if err == _error.INVALID_FIELD:
+            raise ValueError("Invalid inline (%d) or crossline (%d) field/byte offset. "\
+                             "Too large or between valid byte offsets" % (self._il, self._xl))
+
+        if err == _error.INVALID_OFFSETS:
+            raise RuntimeError("Found more offsets than traces. File probably corrupt.")
+
+    def _init_line_count(self):
+        ilines_sz, xlines_sz = ct.c_uint(), ct.c_uint()
+
+        if self.sorting == 1: #crossline sorted
+            fi = self._il
+            l1out = ct.byref(xlines_sz)
+            l2out = ct.byref(ilines_sz)
+        elif self.sorting == 2: #inline sorted
+            fi = self._xl
+            l1out = ct.byref(ilines_sz)
+            l2out = ct.byref(xlines_sz)
+        else:
+            raise RuntimeError("Unable to determine sorting. File probably corrupt.")
+
+        errc = self._count_lines(fi, self.offsets, l1out, l2out, self._tr0, self._bsz)
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(ilines_sz.value), int(xlines_sz.value)
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Error while counting lines: %s", os.strerror(errno))
+
+
+    def _init_ilines(self, iline_count, xline_count):
+        ilines = (ct.c_uint * iline_count)()
+        errc = self._inline_indices(self._il, self.sorting, iline_count, xline_count, self.offsets, ilines, self._tr0, self._bsz)
+        err = _error(errc)
+
+        if err == _error.OK:
+            return map(int, ilines), ilines
+
+        if err == _error.INVALID_SORTING:
+            raise RuntimeError("Unknown file sorting.")
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Error while reading inline indices: %s", os.strerror(errno))
+
+    def _init_iline_length(self, xline_count):
+        length = ct.c_uint()
+        errc = self._inline_length(self.sorting, self.tracecount, xline_count, self.offsets, ct.byref(length))
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(length.value)
+
+        if err == _error.INVALID_SORTING:
+            raise RuntimeError("Unknown file sorting.")
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Error while determining inline length: %s", os.strerror(errno))
+
+    def _init_iline_stride(self, iline_count):
+        stride = ct.c_uint()
+        errc = self._inline_stride(self.sorting, iline_count, ct.byref(stride))
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(stride.value)
+
+        if err == _error.INVALID_SORTING:
+            raise RuntimeError("Unknown file sorting.")
+
+    def _init_xlines(self, iline_count, xline_count):
+        xlines = (ct.c_uint * xline_count)()
+        errc = self._crossline_indices(self._xl, self.sorting, iline_count, xline_count, self.offsets, xlines, self._tr0, self._bsz)
+        err = _error(errc)
+
+        if err == _error.OK:
+            return map(int, xlines), xlines
+
+        if err == _error.INVALID_SORTING:
+            raise RuntimeError("Unknown file sorting.")
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Error while reading crossline indices: %s", os.strerror(errno))
+
+    def _init_xline_length(self, iline_count):
+        length = ct.c_uint()
+        errc = self._crossline_length(self.sorting, self.tracecount, iline_count, self.offsets, ct.byref(length))
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(length.value)
+
+        if err == _error.INVALID_SORTING:
+            raise RuntimeError("Unknown file sorting.")
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Error while determining crossline length: %s", os.strerror(errno))
+
+
+    def _init_xline_stride(self, xline_count):
+        stride = ct.c_uint()
+        errc = self._crossline_stride(self.sorting, xline_count, ct.byref(stride))
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(stride.value)
+
+        if err == _error.INVALID_SORTING:
+            raise RuntimeError("Unknown file sorting.")
+
+    def __enter__(self):
+        """Internal."""
+        return self
+
+    def __exit__(self, type, value, traceback):
+        """Internal."""
+        self.close()
+
+    def flush(self):
+        """Flush a file - write the library buffers to disk.
+
+        This method is mostly useful for testing.
+
+        It is not necessary to call this method unless you want to observe your
+        changes while the file is still open. The file will automatically be
+        flushed for you if you use the `with` statement when your routine is
+        completed.
+
+        Examples:
+            Flush::
+                >>> with segyio.open(path) as f:
+                ...     # write something to f
+                ...     f.flush()
+        """
+        self._flush()
+
+    def close(self):
+        """Close the file.
+
+        This method is mostly useful for testing.
+
+        It is not necessary to call this method if you're using the `with`
+        statement, which will close the file for you.
+        """
+        self._close()
+
+    @property
+    def header(self):
+        """ Interact with segy in header mode.
+
+        This mode gives access to reading and writing functionality of headers,
+        both in individual (trace) mode and line mode. Individual headers are
+        accessed via generators and are not read from or written to disk until
+        the generator is realised and the header in question is used. Supports
+        python slicing (yields a generator), as well as direct lookup.
+
+        Examples:
+            Reading a field in a trace::
+                >>> f.header[10][TraceField.offset]
+
+            Writing a field in a trace::
+                >>> f.header[10][TraceField.offset] = 5
+
+            Copy a header from another header::
+                >>> f.header[28] = f.header[29]
+
+            Reading multiple fields in a trace. If raw, numerical offsets are
+            used they must align with the defined byte offsets by the SEGY
+            specification::
+                >>> f.header[10][TraceField.offset, TraceField.INLINE_3D]
+                >>> f.header[10][37, 189]
+
+            Write multiple fields in a trace::
+                >>> f.header[10] = { 37: 5, TraceField.INLINE_3D: 2484 }
+
+            Iterate over headers and gather line numbers::
+                >>> [h[TraceField.INLINE_3D] for h in f.header]
+                >>> [h[25, 189] for h in f.header]
+
+            Write field in all headers::
+                >>> for h in f.header:
+                ...     h[37] = 1
+                ...     h = { TraceField.offset: 1, 2484: 10 }
+                ...
+
+            Read a field in 10 first headers::
+                >>> [h[25] for h in f.header[0:10]]
+
+            Read a field in every other header::
+                >>> [h[37] for h in f.header[::2]]
+
+            Write a field in every other header::
+                >>> for h in f.header[::2]:
+                ...     h = { TraceField.offset = 2 }
+                ...
+
+            Cache a header:
+                >>> h = f.header[12]
+                >>> x = foo()
+                >>> h[37] = x
+
+            A convenient way for operating on all headers of a file is to use the
+            default full-file range.  It will write headers 0, 1, ..., n, but uses
+            the iteration specified by the right-hand side (i.e. can skip headers
+            etc).
+
+            If the right-hand-side headers are exhausted before all the destination
+            file headers the writing will stop, i.e. not all all headers in the
+            destination file will be written to.
+
+            Copy headers from file f to file g::
+                >>> f.header = g.header
+
+            Set offset field::
+                >>> f.header = { TraceField.offset: 5 }
+
+            Copy every 12th header from the file g to f's 0, 1, 2...::
+                >>> f.header = g.header[::12]
+                >>> f.header[0] == g.header[0]
+                True
+                >>> f.header[1] == g.header[12]
+                True
+                >>> f.header[2] == g.header[2]
+                False
+                >>> f.header[2] == g.header[24]
+                True
+        """
+        return _header(self)
+
+    @header.setter
+    def header(self, val):
+        if isinstance(val, _header.proxy) or isinstance(val, dict):
+            val = itertools.repeat(val)
+
+        h, buf = self.header, None
+        for i, v in itertools.izip(xrange(self.tracecount), val):
+            h[i, buf] = v
+
+    @property
+    def trace(self):
+        """ Interact with segy in trace mode.
+
+        This mode gives access to reading and writing functionality for traces.
+        The primary data type is the numpy ndarray. Traces can be accessed
+        individually or with python slices, and writing is done via assignment.
+
+        All examples use `np` for `numpy`.
+
+        Examples:
+            Read all traces in file f and store in a list::
+                >>> l = [np.copy(tr) for tr in f.trace]
+
+            Do numpy operations on a trace::
+                >>> tr = f.trace[10]
+                >>> tr = np.transpose(tr)
+                >>> tr = tr * 2
+                >>> tr = tr - 100
+                >>> avg = np.average(tr)
+
+            Do numpy operations on every other trace::
+                >>> for tr in f.trace[::2]:
+                ...     print( np.average(tr) )
+                ...
+
+            Traverse traces in reverse::
+                >>> for tr in f.trace[::-1]:
+                ...     print( np.average(tr) )
+                ...
+
+            Double every trace value and write to disk. Since accessing a trace
+            gives a numpy value, to write to the respective trace we need its index::
+                >>> for i, tr in enumerate(f.trace):
+                ...     tr = tr * 2
+                ...     f.trace[i] = tr
+                ...
+
+            Reuse an array for memory efficiency when working with indices.
+            When using slices or full ranges this is done for you::
+                >>> tr = None
+                >>> for i in xrange(100):
+                ...     tr = f.trace[i, tr]
+                ...     tr = tr * 2
+                ...     print(np.average(tr))
+                ...
+
+            Read a value directly from a file. The second [] is numpy access
+            and supports all numpy operations, including negative indexing and
+            slicing::
+                >>> f.trace[0][0]
+                1490.2
+                >>> f.trace[0][1]
+                1490.8
+                >>> f.trace[0][-1]
+                1871.3
+                >>> f.trace[-1][100]
+                1562.0
+
+            Trace mode supports len(), returning the number of traces in a
+            file::
+                >>> len(f.trace)
+                300
+
+            Convenient way for setting traces from 0, 1, ... n, based on the
+            iterable set of traces on the right-hand-side.
+
+            If the right-hand-side traces are exhausted before all the destination
+            file traces the writing will stop, i.e. not all all traces in the
+            destination file will be written.
+
+            Copy traces from file f to file g::
+                >>> f.trace = g.trace.
+
+            Copy first half of the traces from g to f::
+                >>> f.trace = g.trace[:len(g.trace)/2]
+
+            Fill the file with one trace (filled with zeros)::
+                >>> tr = np.zeros(f.samples)
+                >>> f.trace = itertools.repeat(tr)
+        """
+        class trace:
+            def __getitem__(inner, index, buf = None):
+                if isinstance(index, tuple):
+                    return inner.__getitem__(index[0], index[1])
+
+                buf = self._trace_buffer(buf)
+
+                if isinstance(index, int):
+                    if not 0 <= abs(index) < len(inner):
+                        raise IndexError("Trace %d not in range (-%d,%d)", (index, len(inner), len(inner)))
+
+                    return self._readtr(index, buf)
+
+                elif isinstance(index, slice):
+                    def gen():
+                        for i in xrange(*index.indices(len(inner))):
+                            yield self._readtr(i, buf)
+
+                    return gen()
+
+                else:
+                    raise TypeError( "Key must be int, slice, (int,np.ndarray) or (slice,np.ndarray)" )
+
+            def __setitem__(inner, index, val):
+                if not 0 <= abs(index) < len(inner):
+                    raise IndexError("Trace %d not in range (-%d,%d)", (index, len(inner), len(inner)))
+
+                if not isinstance( val, np.ndarray ):
+                    raise TypeError( "Value must be numpy.ndarray" )
+
+                if val.dtype != np.float32:
+                    raise TypeError( "Numpy array must be float32" )
+
+                shape = (self.samples,)
+
+                if val.shape[0] < shape[0]:
+                    raise TypeError( "Array wrong shape. Expected minimum %s, was %s" % (shape, val.shape))
+
+                if isinstance(index, int):
+                    self._writetr(index, val)
+
+                elif isinstance(index, slice):
+                    for i, buf in xrange(*index.indices(len(inner))), val:
+                        self._writetr(i, val)
+
+                else:
+                    raise KeyError( "Wrong shape of index" )
+
+
+            def __len__(inner):
+                return self.tracecount
+
+            def __iter__(inner):
+                return inner[:]
+
+        return trace()
+
+    @trace.setter
+    def trace(self, val):
+        tr = self.trace
+        for i, v in itertools.izip(xrange(len(tr)), val):
+            tr[i] = v
+
+    def _line_buffer(self, length, buf = None):
+        shape = (length, self.samples)
+
+        if buf is None:
+            return np.empty(shape = shape, dtype = np.float32)
+
+        if not isinstance(buf, np.ndarray):
+            return buf
+
+        if buf.dtype != np.float32:
+            return np.empty(shape = shape, dtype = np.float32)
+
+        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 _fread_trace0(self, lineno, length, stride, linenos, line_type):
+        line0 = ct.c_uint()
+        errc = self._line_trace0(lineno, length, stride, linenos, len(linenos), ct.byref(line0))
+        err = _error(errc)
+
+        if err == _error.OK:
+            return int(line0.value)
+
+        if errc == _error.MISSING_LINE_INDEX:
+            raise KeyError("%s number %d does not exist." % (line_type, lineno))
+
+        errno = ct.get_errno()
+        raise OSError( errno, "Unable to read line %d: %s" % (lineno, os.strerror(errno)))
+
+    def _fread_line(self, trace0, length, stride, buf):
+        errc = self._read_line(trace0, length, stride, _floatp(buf), self._tr0, self._bsz)
+        err = _error(errc)
+
+        if err != _error.OK:
+            errno = ct.get_errno()
+            raise OSError(errno, "Unable to read line starting at trace %d: %s" % (trace0, os.strerror(errno)))
+
+        errc = self._to_native(self._fmt, buf.size, _floatp(buf))
+        err = _error(errc)
+
+        if err == _error.OK:
+            return buf
+
+        raise BufferError("Unable to convert line to native float")
+
+
+    def _fwrite_line(self, trace0, length, stride, buf):
+        errc_conv = self._from_native(self._fmt, buf.size, _floatp(buf))
+        err_conv = _error(errc_conv)
+
+        if err_conv != _error.OK:
+            raise BufferError("Unable to convert line from native float.")
+
+        errc = self._write_line(trace0, length, stride, _floatp(buf), self._tr0, self._bsz)
+        errc_conv = self._to_native(self._fmt, buf.size, _floatp(buf))
+
+        err = _error(errc)
+        err_conv = _error(errc_conv)
+
+        if err != _error.OK:
+            errno = ct.get_errno()
+            raise OSError(errno, "Error writing line starting at trace %d: %s" % (trace0, os.strerror(errno)))
+
+        if err_conv != _error.OK:
+            raise BufferError("Unable to convert line from native float.")
+
+    @property
+    def iline(self):
+        """ Interact with segy in inline mode.
+
+        This mode gives access to reading and writing functionality for inlines.
+        The primary data type is the numpy ndarray. Inlines can be accessed
+        individually or with python slices, and writing is done via assignment.
+        Note that accessing inlines uses the line numbers, not their position,
+        so if a files has inlines [2400..2500], accessing line [0..100] will be
+        an error. Note that each line is returned as a numpy array, meaning
+        accessing the intersections of the inline and crossline is 0-indexed.
+
+        Examples:
+            Read an inline::
+                >>> il = f.iline[2400]
+
+            Copy every inline into a list::
+                >>> l = [np.copy(x) for x in f.iline]
+
+            The number of inlines in a file::
+                >>> len(f.iline)
+
+            Numpy operations on every other inline::
+                >>> for line in f.iline[::2]:
+                ...     line = line * 2
+                ...     avg = np.average(line)
+                ...     print(avg)
+                ...
+
+            Read inlines up to 2430::
+                >>> for line in f.iline[:2430]:
+                ...     print(np.average(line))
+                ...
+
+            Copy a line from file g to f::
+                >>> f.iline[2400] = g.iline[2834]
+
+            Copy lines from the first line in g to f, starting at 2400,
+            ending at 2410 in f::
+                >>> f.iline[2400:2411] = g.iline
+
+            Convenient way for setting inlines, from left-to-right as the inline
+            numbers are specified in the file.ilines property, from an iterable
+            set on the right-hand-side.
+
+            If the right-hand-side inlines are exhausted before all the destination
+            file inlines the writing will stop, i.e. not all all inlines in the
+            destination file will be written.
+
+            Copy inlines from file f to file g::
+                >>> f.iline = g.iline.
+
+            Copy first half of the inlines from g to f::
+                >>> f.iline = g.iline[:g.ilines[len(g.ilines)/2]]
+
+            Copy every other inline from a different file::
+                >>> f.iline = g.iline[::2]
+        """
+        il_len, il_stride = self._iline_length, self._iline_stride
+        lines, raw_lines = self.ilines, self._raw_ilines
+        other_lines = self.xlines
+        buffn = lambda x = None: self._line_buffer(il_len, x)
+        readfn = self._fread_line
+
+        def writefn(t0, length, step, val):
+            val = buffn(val)
+            for i, v in itertools.izip(xrange(t0, t0 + step*length, step), val):
+                self._writetr(i, v)
+
+        return _line(self, il_len, il_stride, lines, raw_lines, other_lines, buffn, readfn, writefn, "Inline")
+
+    @iline.setter
+    def iline(self, value):
+        self.iline[:] = value
+
+    @property
+    def xline(self):
+        """ Interact with segy in crossline mode.
+
+        This mode gives access to reading and writing functionality for crosslines.
+        The primary data type is the numpy ndarray. crosslines can be accessed
+        individually or with python slices, and writing is done via assignment.
+        Note that accessing crosslines uses the line numbers, not their position,
+        so if a files has crosslines [1400..1450], accessing line [0..100] will be
+        an error. Note that each line is returned as a numpy array, meaning
+        accessing the intersections of the crossline and crossline is 0-indexed.
+
+        Examples:
+            Read an crossline::
+                >>> il = f.xline[1400]
+
+            Copy every crossline into a list::
+                >>> l = [np.copy(x) for x in f.xline]
+
+            The number of crosslines in a file::
+                >>> len(f.xline)
+
+            Numpy operations on every third crossline::
+                >>> for line in f.xline[::3]:
+                ...     line = line * 6
+                ...     avg = np.average(line)
+                ...     print(avg)
+                ...
+
+            Read crosslines up to 1430::
+                >>> for line in f.xline[:1430]:
+                ...     print(np.average(line))
+                ...
+
+            Copy a line from file g to f::
+                >>> f.xline[1400] = g.xline[1603]
+
+            Copy lines from the first line in g to f, starting at 1400,
+            ending at 1415 in f::
+                >>> f.xline[1400:1416] = g.xline
+
+
+            Convenient way for setting crosslines, from left-to-right as the crossline
+            numbers are specified in the file.xlines property, from an iterable
+            set on the right-hand-side.
+
+            If the right-hand-side crosslines are exhausted before all the destination
+            file crosslines the writing will stop, i.e. not all all crosslines in the
+            destination file will be written.
+
+            Copy crosslines from file f to file g::
+                >>> f.xline = g.xline.
+
+            Copy first half of the crosslines from g to f::
+                >>> f.xline = g.xline[:g.xlines[len(g.xlines)/2]]
+
+            Copy every other crossline from a different file::
+                >>> f.xline = g.xline[::2]
+        """
+        xl_len, xl_stride = self._xline_length, self._xline_stride
+        lines, raw_lines = self.xlines, self._raw_xlines
+        other_lines = self.ilines
+        buffn = lambda x = None: self._line_buffer(xl_len, x)
+        readfn = self._fread_line
+
+        def writefn(t0, length, step, val):
+            val = buffn(val)
+            for i, v in itertools.izip(xrange(t0, t0 + step*length, step), val):
+                self._writetr(i, v)
+
+        return _line(self, xl_len, xl_stride, lines, raw_lines, other_lines, buffn, readfn, writefn, "Crossline")
+
+    @xline.setter
+    def xline(self, value):
+        self.xline[:] = value
+
+    def _readh(self, index, buf = None):
+        errc = self._read_header(index, buf, self._tr0, self._bsz)
+        err = _error(errc)
+
+        if err != _error.OK:
+            errno = ct.get_errno()
+            raise OSError(errno, os.strerror(errno))
+
+        return buf
+
+    def _trace_buffer(self, buf = None):
+        samples = self.samples
+
+        if buf is None:
+            buf = np.empty( shape = samples, dtype = np.float32 )
+        elif not isinstance( buf, np.ndarray ):
+            raise TypeError("Buffer must be None or numpy.ndarray" )
+        elif buf.dtype != np.float32:
+            buf = np.empty( shape = samples, dtype = np.float32 )
+        elif buf.shape != samples:
+            buf.reshape( samples )
+
+        return buf
+
+    def _readtr(self, traceno, buf = None):
+        if traceno < 0:
+            traceno += self.tracecount
+
+        buf = self._trace_buffer(buf)
+        bufp = _floatp(buf)
+
+        errc = self._read_trace(traceno, bufp, self._tr0, self._bsz)
+        err = _error(errc)
+
+        if err != _error.OK:
+            errno = ct.get_errno()
+            raise OSError(errno, "Could not read trace %d: %s" % (traceno, os.strerror(errno)))
+
+        errc = self._to_native(self._fmt, self.samples, bufp)
+        err = _error(errc)
+
+        if err == _error.OK:
+            return buf
+
+        raise BufferError("Error converting to native float.")
+
+    def _writetr(self, traceno, buf):
+        bufp = _floatp(buf)
+        errc = self._from_native(self._fmt, self.samples, bufp)
+        err = _error(errc)
+
+        if err != _error.OK:
+            raise BufferError("Error converting from native float.")
+
+        errc = self._write_trace(traceno, bufp, self._tr0, self._bsz)
+        errc_conv = self._to_native(self._fmt, self.samples, bufp)
+
+        err, err_conv = _error(errc), _error(errc_conv)
+
+        if err != _error.OK and err_conv != SEGY_OK:
+            errno = ct.get_errno()
+            raise OSError(errno, "Writing trace failed, and array integrity can not be guaranteed: %s" % os.strerror(errno))
+
+        if err != _error.OK:
+            errno = ct.get_errno()
+            raise OSError(errno, "Error writing trace %d: %s" % (traceno, os.strerror(errno)))
+
+        if err_conv != _error.OK:
+            raise BufferError("Could convert to native float. The array integrety can not be guaranteed.")
+
+    @property
+    def text(self):
+        """ Interact with segy in text mode.
+
+        This mode gives access to reading and writing functionality for textual
+        headers.
+
+        The primary data type is the python string. Reading textual headers is
+        done via [], and writing is done via assignment. No additional
+        structure is built around the textual header, so everything is treated
+        as one long string without line breaks.
+
+        Examples:
+            Print the textual header::
+                >>> print(f.text[0])
+
+            Print the first extended textual header::
+                >>> print(f.text[1])
+
+            Write a new textual header::
+                >>> f.text[0] = make_new_header()
+
+            Print a textual header line-by-line::
+                >>> # using zip, from the zip documentation
+                >>> text = f.text[0]
+                >>> lines = map(''.join, zip( *[iter(text)] * 80))
+                >>> for line in lines:
+                ...     print(line)
+                ...
+        """
+        class text:
+            def __init__(inner):
+                inner.size = self._textsize()
+
+            def __getitem__(inner, index):
+                if index > self.ext_headers:
+                    raise IndexError("Textual header %d not in file" % index)
+
+                buf = ct.create_string_buffer(inner.size)
+                err = self._texthdr( buf )
+
+                if err == 0: return buf.value
+
+                errno = ct.get_errno()
+                raise OSError(errno, "Could not read text header: %s" % os.strerror(errno))
+
+            def __setitem__(inner, index, val):
+                if index > self.ext_headers:
+                    raise IndexError("Textual header %d not in file" % index)
+
+                buf = ct.create_string_buffer(inner.size)
+                for i, x in enumerate(val[:inner.size]):
+                    buf[i] = x
+
+                err = self._write_texthdr(index, buf)
+
+                if err == 0: return
+
+                errno = ct.get_errno()
+                raise OSError(errno, "Could not write text header: %s" % os.strerror(errno))
+
+        return text()
+
+    @property
+    def bin(self):
+        """ Interact with segy in binary mode.
+
+        This mode gives access to reading and writing functionality for the
+        binary header. Please note that using numeric binary offsets uses the
+        offset numbers from the specification, i.e. the first field of the
+        binary header starts at 3201, not 1. If you're using the enumerations
+        this is handled for you.
+
+        Examples:
+            Copy a header from file g to file f::
+                >>> f.bin = g.bin
+
+            Reading a field in a trace::
+                >>> traces_per_ensemble = f.bin[3213]
+
+            Writing a field in a trace::
+                >>> f.bin[BinField.Traces] = 5
+
+            Reading multiple fields::
+                >>> d = f.bin[BinField.Traces, 3233]
+
+            Copy a field from file g to file f::
+                >>> f.bin[BinField.Format] = g.bin[BinField.Format]
+
+            Copy full binary from file f to file g::
+                >>> f.bin = g.bin
+
+            Copy multiple fields from file f to file g::
+                >>> f.bin = g.bin[BinField.Traces, 3233]
+
+            Write field in binary header via dict::
+                >>> f.bin = { BinField.Traces: 350 }
+
+            Write multiple fields in a trace::
+                >>> f.bin = { 3213: 5, BinField.SweepFrequencyStart: 17 }
+        """
+        def get_bfield(buf, field, val):
+            errc = self._get_bfield(buf, int(field), ct.byref(val))
+            err = _error(errc)
+
+            if err != _error.OK:
+                raise IndexError("Invalid byte offset %d" % field)
+
+            return int(val.value)
+
+        def set_bfield(buf, field, val):
+            errc = self._set_bfield(buf, int(field), val)
+            err = _error(errc)
+
+            if err != _error.OK:
+                raise IndexError("Invalid byte offset %d" % field)
+
+        buf = (ct.c_char * self._binheader_size())()
+        gt = get_bfield
+        st = set_bfield
+        wr = self._write_binheader
+        ty = BinField
+
+        err = self._binheader(buf)
+        if err == 0:
+            return _header.proxy(buf, get_field = gt, set_field = st, write = wr, field_type = ty)
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Could not read binary header: %s" % os.strerror(errno))
+
+    @bin.setter
+    def bin(self, value):
+        try:
+            buf = self.bin.buf
+
+        except OSError:
+            # the file was probably newly created and the binary header hasn't
+            # been written yet.  if this is the case we want to try and write
+            # it. if the file was broken, permissions were wrong etc writing
+            # will fail too
+            buf = (ct.c_char * self._binheader_size())()
+
+        if isinstance(value, dict):
+            for k, v in value.items():
+                self._set_bfield(buf, int(k), v)
+        else:
+            buf = value.buf
+
+        err = self._write_binheader(buf)
+        if err == 0: return
+
+        errno = ct.get_errno()
+        raise OSError(errno, "Could not write text header: %s" % os.strerror(errno))
+
+    @property
+    def format(self):
+        d = {
+                1: "4-byte IBM float",
+                2: "4-byte signed integer",
+                3: "2-byte signed integer",
+                4: "4-byte fixed point with gain",
+                5: "4-byte IEEE float",
+                8: "1-byte signed char"
+            }
+
+        class fmt:
+            def __int__(inner):
+                return self._fmt
+
+            def __str__(inner):
+                if not self._fmt in d:
+                    return "Unknown format"
+
+                return d[ self._fmt ]
+
+        return fmt()
+
+    def free(self):
+        """Internal."""
+        pass
+
+class spec:
+    def __init__(self):
+        self.iline          = 189
+        self.ilines         = None
+        self.xline          = 193
+        self.xlines         = None
+        self.offsets        = 1
+        self.samples        = None
+        self.tracecount     = None
+        self.ext_headers    = 0
+        self.format         = None
+        self.sorting        = None
+        self.t0             = 1111.0
diff --git a/python/segyio/segysampleformat.py b/python/segyio/segysampleformat.py
new file mode 100644
index 0000000..81abc8a
--- /dev/null
+++ b/python/segyio/segysampleformat.py
@@ -0,0 +1,24 @@
+from cwrap import BaseCEnum
+
+
+class SegySampleFormat(BaseCEnum):
+    TYPE_NAME = "SegySampleFormat"
+
+    IBM_FLOAT_4_BYTE = None
+    SIGNED_INTEGER_4_BYTE = None
+    SIGNED_SHORT_2_BYTE = None
+    FIXED_POINT_WITH_GAIN_4_BYTE = None
+    IEEE_FLOAT_4_BYTE = None
+    NOT_IN_USE_1 = None
+    NOT_IN_USE_2 = None
+    SIGNED_CHAR_1_BYTE = None
+
+SegySampleFormat.addEnum("IBM_FLOAT_4_BYTE", 1)
+SegySampleFormat.addEnum("SIGNED_INTEGER_4_BYTE", 2)
+SegySampleFormat.addEnum("SIGNED_SHORT_2_BYTE", 3)
+SegySampleFormat.addEnum("FIXED_POINT_WITH_GAIN_4_BYTE", 4)
+SegySampleFormat.addEnum("IEEE_FLOAT_4_BYTE", 5)
+SegySampleFormat.addEnum("NOT_IN_USE_1", 6)
+SegySampleFormat.addEnum("NOT_IN_USE_2", 7)
+SegySampleFormat.addEnum("SIGNED_CHAR_1_BYTE", 8)
+
diff --git a/python/segyio/tracefield.py b/python/segyio/tracefield.py
new file mode 100644
index 0000000..5223a1a
--- /dev/null
+++ b/python/segyio/tracefield.py
@@ -0,0 +1,189 @@
+from cwrap import BaseCEnum
+
+
+class TraceField(BaseCEnum):
+    TYPE_NAME = "TraceField"
+
+    TRACE_SEQUENCE_LINE = None
+    TRACE_SEQUENCE_FILE = None
+    FieldRecord = None
+    TraceNumber = None
+    EnergySourcePoint = None
+    CDP = None
+    CDP_TRACE = None
+    TraceIdentificationCode = None
+    NSummedTraces = None
+    NStackedTraces = None
+    DataUse = None
+    offset = None
+    ReceiverGroupElevation = None
+    SourceSurfaceElevation = None
+    SourceDepth = None
+    ReceiverDatumElevation = None
+    SourceDatumElevation = None
+    SourceWaterDepth = None
+    GroupWaterDepth = None
+    ElevationScalar = None
+    SourceGroupScalar = None
+    SourceX = None
+    SourceY = None
+    GroupX = None
+    GroupY = None
+    CoordinateUnits = None
+    WeatheringVelocity = None
+    SubWeatheringVelocity = None
+    SourceUpholeTime = None
+    GroupUpholeTime = None
+    SourceStaticCorrection = None
+    GroupStaticCorrection = None
+    TotalStaticApplied = None
+    LagTimeA = None
+    LagTimeB = None
+    DelayRecordingTime = None
+    MuteTimeStart = None
+    MuteTimeEND = None
+    TRACE_SAMPLE_COUNT = None
+    TRACE_SAMPLE_INTERVAL = None
+    GainType = None
+    InstrumentGainConstant = None
+    InstrumentInitialGain = None
+    Correlated = None
+    SweepFrequencyStart = None
+    SweepFrequencyEnd = None
+    SweepLength = None
+    SweepType = None
+    SweepTraceTaperLengthStart = None
+    SweepTraceTaperLengthEnd = None
+    TaperType = None
+    AliasFilterFrequency = None
+    AliasFilterSlope = None
+    NotchFilterFrequency = None
+    NotchFilterSlope = None
+    LowCutFrequency = None
+    HighCutFrequency = None
+    LowCutSlope = None
+    HighCutSlope = None
+    YearDataRecorded = None
+    DayOfYear = None
+    HourOfDay = None
+    MinuteOfHour = None
+    SecondOfMinute = None
+    TimeBaseCode = None
+    TraceWeightingFactor = None
+    GeophoneGroupNumberRoll1 = None
+    GeophoneGroupNumberFirstTraceOrigField = None
+    GeophoneGroupNumberLastTraceOrigField = None
+    GapSize = None
+    OverTravel = None
+    CDP_X = None
+    CDP_Y = None
+    INLINE_3D = None
+    CROSSLINE_3D = None
+    ShotPoint = None
+    ShotPointScalar = None
+    TraceValueMeasurementUnit = None
+    TransductionConstantMantissa = None
+    TransductionConstantPower = None
+    TransductionUnit = None
+    TraceIdentifier = None
+    ScalarTraceHeader = None
+    SourceType = None
+    SourceEnergyDirectionMantissa = None
+    SourceEnergyDirectionExponent = None
+    SourceMeasurementMantissa = None
+    SourceMeasurementExponent = None
+    SourceMeasurementUnit = None
+    UnassignedInt1 = None
+    UnassignedInt2 = None
+
+TraceField.addEnum("TRACE_SEQUENCE_LINE", 1)
+TraceField.addEnum("TRACE_SEQUENCE_FILE", 5)
+TraceField.addEnum("FieldRecord", 9)
+TraceField.addEnum("TraceNumber", 13)
+TraceField.addEnum("EnergySourcePoint", 17)
+TraceField.addEnum("CDP", 21)
+TraceField.addEnum("CDP_TRACE", 25)
+TraceField.addEnum("TraceIdentificationCode", 29)
+TraceField.addEnum("NSummedTraces", 31)
+TraceField.addEnum("NStackedTraces", 33)
+TraceField.addEnum("DataUse", 35)
+TraceField.addEnum("offset", 37)
+TraceField.addEnum("ReceiverGroupElevation", 41)
+TraceField.addEnum("SourceSurfaceElevation", 45)
+TraceField.addEnum("SourceDepth", 49)
+TraceField.addEnum("ReceiverDatumElevation", 53)
+TraceField.addEnum("SourceDatumElevation", 57)
+TraceField.addEnum("SourceWaterDepth", 61)
+TraceField.addEnum("GroupWaterDepth", 65)
+TraceField.addEnum("ElevationScalar", 69)
+TraceField.addEnum("SourceGroupScalar", 71)
+TraceField.addEnum("SourceX", 73)
+TraceField.addEnum("SourceY", 77)
+TraceField.addEnum("GroupX", 81)
+TraceField.addEnum("GroupY", 85)
+TraceField.addEnum("CoordinateUnits", 89)
+TraceField.addEnum("WeatheringVelocity", 91)
+TraceField.addEnum("SubWeatheringVelocity", 93)
+TraceField.addEnum("SourceUpholeTime", 95)
+TraceField.addEnum("GroupUpholeTime", 97)
+TraceField.addEnum("SourceStaticCorrection", 99)
+TraceField.addEnum("GroupStaticCorrection", 101)
+TraceField.addEnum("TotalStaticApplied", 103)
+TraceField.addEnum("LagTimeA", 105)
+TraceField.addEnum("LagTimeB", 107)
+TraceField.addEnum("DelayRecordingTime", 109)
+TraceField.addEnum("MuteTimeStart", 111)
+TraceField.addEnum("MuteTimeEND", 113)
+TraceField.addEnum("TRACE_SAMPLE_COUNT", 115)
+TraceField.addEnum("TRACE_SAMPLE_INTERVAL", 117)
+TraceField.addEnum("GainType", 119)
+TraceField.addEnum("InstrumentGainConstant", 121)
+TraceField.addEnum("InstrumentInitialGain", 123)
+TraceField.addEnum("Correlated", 125)
+TraceField.addEnum("SweepFrequencyStart", 127)
+TraceField.addEnum("SweepFrequencyEnd", 129)
+TraceField.addEnum("SweepLength", 131)
+TraceField.addEnum("SweepType", 133)
+TraceField.addEnum("SweepTraceTaperLengthStart", 135)
+TraceField.addEnum("SweepTraceTaperLengthEnd", 137)
+TraceField.addEnum("TaperType", 139)
+TraceField.addEnum("AliasFilterFrequency", 141)
+TraceField.addEnum("AliasFilterSlope", 143)
+TraceField.addEnum("NotchFilterFrequency", 145)
+TraceField.addEnum("NotchFilterSlope", 147)
+TraceField.addEnum("LowCutFrequency", 149)
+TraceField.addEnum("HighCutFrequency", 151)
+TraceField.addEnum("LowCutSlope", 153)
+TraceField.addEnum("HighCutSlope", 155)
+TraceField.addEnum("YearDataRecorded", 157)
+TraceField.addEnum("DayOfYear", 159)
+TraceField.addEnum("HourOfDay", 161)
+TraceField.addEnum("MinuteOfHour", 163)
+TraceField.addEnum("SecondOfMinute", 165)
+TraceField.addEnum("TimeBaseCode", 167)
+TraceField.addEnum("TraceWeightingFactor", 169)
+TraceField.addEnum("GeophoneGroupNumberRoll1", 171)
+TraceField.addEnum("GeophoneGroupNumberFirstTraceOrigField", 173)
+TraceField.addEnum("GeophoneGroupNumberLastTraceOrigField", 175)
+TraceField.addEnum("GapSize", 177)
+TraceField.addEnum("OverTravel", 179)
+TraceField.addEnum("CDP_X", 181)
+TraceField.addEnum("CDP_Y", 185)
+TraceField.addEnum("INLINE_3D", 189)
+TraceField.addEnum("CROSSLINE_3D", 193)
+TraceField.addEnum("ShotPoint", 197)
+TraceField.addEnum("ShotPointScalar", 201)
+TraceField.addEnum("TraceValueMeasurementUnit", 203)
+TraceField.addEnum("TransductionConstantMantissa", 205)
+TraceField.addEnum("TransductionConstantPower", 209)
+TraceField.addEnum("TransductionUnit", 211)
+TraceField.addEnum("TraceIdentifier", 213)
+TraceField.addEnum("ScalarTraceHeader", 215)
+TraceField.addEnum("SourceType", 217)
+TraceField.addEnum("SourceEnergyDirectionMantissa", 219)
+TraceField.addEnum("SourceEnergyDirectionExponent", 223)
+TraceField.addEnum("SourceMeasurementMantissa", 225)
+TraceField.addEnum("SourceMeasurementExponent", 229)
+TraceField.addEnum("SourceMeasurementUnit", 231)
+TraceField.addEnum("UnassignedInt1", 233)
+TraceField.addEnum("UnassignedInt2", 237)
\ No newline at end of file
diff --git a/python/segyio/tracesortingformat.py b/python/segyio/tracesortingformat.py
new file mode 100644
index 0000000..3e6846b
--- /dev/null
+++ b/python/segyio/tracesortingformat.py
@@ -0,0 +1,13 @@
+from cwrap import BaseCEnum
+
+
+class TraceSortingFormat(BaseCEnum):
+    TYPE_NAME = "TraceSortingFormat"
+
+    UNKNOWN_SORTING = None
+    CROSSLINE_SORTING = None
+    INLINE_SORTING = None
+
+TraceSortingFormat.addEnum("UNKNOWN_SORTING", -1)
+TraceSortingFormat.addEnum("CROSSLINE_SORTING", 0)
+TraceSortingFormat.addEnum("INLINE_SORTING", 1)
diff --git a/src/applications/segyinfo.c b/src/applications/segyinfo.c
new file mode 100644
index 0000000..e098b40
--- /dev/null
+++ b/src/applications/segyinfo.c
@@ -0,0 +1,141 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <segyio/segy.h>
+
+static void printSegyTraceInfo( const char* buf ) {
+    int cdp, tsf, xl, il;
+    segy_get_field( buf, CDP, &cdp );
+    segy_get_field( buf, TRACE_SEQUENCE_FILE, &tsf );
+    segy_get_field( buf, CROSSLINE_3D, &xl );
+    segy_get_field( buf, INLINE_3D, &il );
+
+    printf("cdp:               %d\n", cdp );
+    printf("TraceSequenceFile: %d\n", tsf );
+    printf("Crossline3D:       %d\n", xl );
+    printf("Inline3D:          %d\n", il );
+}
+
+static inline int min( int x, int y ) {
+    return x < y ? x : y;
+}
+
+static inline int max( int x, int y ) {
+    return x > y ? x : y;
+}
+
+int main(int argc, char* argv[]) {
+    
+    if( argc != 2 ) {
+        puts("Missing argument, expected run signature:");
+        printf("  %s <segy_file>\n", argv[0]);
+        exit(1);
+    }
+
+    FILE* fp = fopen( argv[ 1 ], "r" );
+    if( !fp ) {
+        perror( "fopen():" );
+        exit( 3 );
+    }
+
+    int err;
+    char header[ SEGY_BINARY_HEADER_SIZE ];
+    err = segy_binheader( fp, header );
+
+    if( err != 0 ) {
+        perror( "Unable to read segy binary header:" );
+        exit( err );
+    }
+
+    const int format = segy_format( header );
+    const unsigned int samples = segy_samples( header );
+    const long trace0 = segy_trace0( header );
+    const unsigned int trace_bsize = segy_trace_bsize( samples );
+    int extended_headers;
+    err = segy_get_bfield( header, BIN_ExtendedHeaders, &extended_headers );
+
+    if( err != 0 ) {
+        perror( "Can't read 'extended headers' field from binary header" );
+        exit( err );
+    }
+
+    size_t traces;
+    err = segy_traces( fp, &traces, trace0, trace_bsize );
+
+    if( err != 0 ) {
+        perror( "Could not determine traces" );
+        exit( err );
+    }
+
+    printf( "Sample format: %d\n", format );
+    printf( "Samples per trace: %d\n", samples );
+    printf( "Traces: %zu\n", traces );
+    printf("Extended text header count: %d\n", extended_headers );
+    puts("");
+
+
+    char traceh[ SEGY_TRACE_HEADER_SIZE ];
+    err = segy_traceheader( fp, 0, traceh, trace0, trace_bsize );
+    if( err != 0 ) {
+        perror( "Unable to read trace 0:" );
+        exit( err );
+    }
+
+    puts("Info from first trace:");
+    printSegyTraceInfo( traceh );
+
+    err = segy_traceheader( fp, 1, traceh, trace0, trace_bsize );
+    if( err != 0 ) {
+        perror( "Unable to read trace 1:" );
+        exit( err );
+    }
+
+    puts("");
+    puts("Info from second trace:");
+    printSegyTraceInfo( traceh );
+
+    clock_t start = clock();
+    int min_sample_count = 999999999;
+    int max_sample_count = 0;
+    for( int i = 0; i < (int)traces; i++ ) {
+        err = segy_traceheader( fp, i, traceh, trace0, trace_bsize );
+        if( err != 0 ) {
+            perror( "Unable to read trace" );
+            exit( err );
+        }
+
+        int samples;
+        err = segy_get_field( traceh, TRACE_SAMPLE_COUNT, &samples );
+
+        if( err != 0 ) {
+            fprintf( stderr, "Invalid trace header field: %d\n", TRACE_SAMPLE_COUNT );
+            exit( err );
+        }
+
+        min_sample_count = min( samples, min_sample_count );
+        max_sample_count = max( samples, max_sample_count );
+    }
+
+    puts("");
+    puts("Info from last trace:");
+    err = segy_traceheader( fp, traces - 1, traceh, trace0, trace_bsize );
+
+    if( err != 0 ) {
+        perror( "Unable to read trace." );
+        exit( err );
+    }
+
+    printSegyTraceInfo( traceh );
+
+    puts("");
+    printf("Min sample count: %d\n", min_sample_count);
+    printf("Max sample count: %d\n", max_sample_count);
+    puts("");
+
+    clock_t diff = clock() - start;
+    printf("Read all trace headers in: %.2f s\n", (double) diff / CLOCKS_PER_SEC);
+
+    fclose( fp );
+    return 0;
+}
diff --git a/src/applications/segyinspect.c b/src/applications/segyinspect.c
new file mode 100644
index 0000000..b2c1b4a
--- /dev/null
+++ b/src/applications/segyinspect.c
@@ -0,0 +1,167 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <segyio/segy.h>
+
+
+static const char* getSampleFormatName( int format ) {
+    switch( format ) {
+        case IBM_FLOAT_4_BYTE:
+            return "IBM Float";         
+        case SIGNED_INTEGER_4_BYTE:
+            return "Int 32";         
+        case SIGNED_SHORT_2_BYTE:
+            return "Int 16";         
+        case FIXED_POINT_WITH_GAIN_4_BYTE:
+            return "Fixed Point with gain (Obsolete)";         
+        case IEEE_FLOAT_4_BYTE:
+            return "IEEE Float";         
+        case NOT_IN_USE_1:
+            return "Not in Use 1";         
+        case NOT_IN_USE_2:
+            return "Not in Use 2";         
+        case SIGNED_CHAR_1_BYTE:
+            return "Int 8";         
+        default:
+            return "Unknown";
+    }
+}
+
+static char* getFastestDirectionName( int sorting ) {
+    if ( sorting == CROSSLINE_SORTING) {
+        return "CROSSLINE";
+    } else {
+        return "INLINE_SORTING";
+    }
+}
+
+int main(int argc, char* argv[]) {
+
+    if (!(argc == 2 || argc == 4)) {
+        puts("Missing argument, expected run signature:");
+        printf("  %s <segy_file> [INLINE_BYTE CROSSLINE_BYTE]\n", argv[0]);
+        printf("  Inline and crossline bytes default to: 189 and 193\n");
+        exit(1);
+    }
+
+    int xl_field = CROSSLINE_3D;
+    int il_field = INLINE_3D;
+
+    if (argc == 4) {
+        il_field = atoi(argv[2]);
+        xl_field = atoi(argv[3]);
+    }
+
+    clock_t start = clock();
+
+    FILE* fp = fopen( argv[ 1 ], "r" );
+    if( !fp ) {
+        perror( "fopen()" );
+        exit( SEGY_FOPEN_ERROR );
+    }
+
+    int err;
+    char header[ SEGY_BINARY_HEADER_SIZE ];
+    err = segy_binheader( fp, header );
+
+    if( err != 0 ) {
+        perror( "Unable to read segy binary header" );
+        exit( err );
+    }
+
+    const int format = segy_format( header );
+    const unsigned int samples = segy_samples( header );
+    const long trace0 = segy_trace0( header );
+    const unsigned int trace_bsize = segy_trace_bsize( samples );
+
+    size_t traces;
+    err = segy_traces( fp, &traces, trace0, trace_bsize );
+
+    if( err != 0 ) {
+        perror( "Could not determine traces" );
+        exit( err );
+    }
+
+    int sorting;
+    err = segy_sorting( fp, il_field, xl_field, &sorting, trace0, trace_bsize );
+    if( err != 0 ) {
+        perror( "Could not determine sorting" );
+        exit( err );
+    }
+
+    unsigned int offsets;
+    err = segy_offsets( fp, il_field, xl_field, traces, &offsets, trace0, trace_bsize );
+    if( err != 0 ) {
+        perror( "Could not determine offsets" );
+        exit( err );
+    }
+
+    unsigned int inline_count, crossline_count;
+    if( sorting == INLINE_SORTING ) {
+        err = segy_count_lines( fp, xl_field, offsets, &inline_count, &crossline_count, trace0, trace_bsize );
+    } else {
+        err = segy_count_lines( fp, il_field, offsets, &crossline_count, &inline_count, trace0, trace_bsize );
+    }
+
+    if( err != 0 ) {
+        fprintf( stderr, "Errcode %d\n", err );
+        perror( "Could not count lines" );
+        exit( err );
+    }
+
+    unsigned int* inline_indices = malloc( sizeof( unsigned int ) * inline_count );
+    unsigned int* crossline_indices = malloc( sizeof( unsigned int ) * crossline_count );
+
+    err = segy_inline_indices( fp, il_field, sorting, inline_count, crossline_count, offsets, inline_indices, trace0, trace_bsize );
+    if( err != 0 ) {
+        perror( "Could not determine inline numbers" );
+        exit( err );
+    }
+
+    err = segy_crossline_indices( fp, xl_field, sorting, inline_count, crossline_count, offsets, crossline_indices, trace0, trace_bsize );
+    if( err != 0 ) {
+        fprintf( stderr, "Errcode %d\n", err );
+        perror( "Could not determine crossline numbers" );
+        exit( err );
+    }
+
+    clock_t diff = clock() - start;
+
+    printf( "Crosslines..........: %d\n", crossline_count);
+    printf( "Inlines.............: %d\n", inline_count);
+    printf( "Offsets.............: %d\n", offsets);
+    printf( "Samples.............: %d\n", samples);
+    printf( "Sample format.......: %s\n", getSampleFormatName( format ) );
+    printf( "Fastest direction...: %s\n", getFastestDirectionName( sorting ) );
+
+
+    puts("");
+    puts("Crossline indexes:");
+
+    for( unsigned int i = 0; i < crossline_count; i++ ) {
+        printf( "%d ", crossline_indices[i] );
+    }
+
+    puts("\n");
+    puts("Inline indexes:");
+
+    for( unsigned int i = 0; i < inline_count; i++ ) {
+        printf( "%d ", inline_indices[i] );
+    }
+
+    puts("\n");
+    puts("Sample indexes:");
+
+    //for (int i = 0; i < spec->sample_count; i++) {
+    //    printf("%.2f ", spec->sample_indexes[i]);
+    //}
+    puts("\n");
+
+    printf("Inspection took : %.2f s\n", (double) diff / CLOCKS_PER_SEC);
+
+    free( inline_indices );
+    free( crossline_indices );
+    fclose( fp );
+
+    exit(0);
+}
diff --git a/src/segyio/segy.c b/src/segyio/segy.c
new file mode 100644
index 0000000..5198b7f
--- /dev/null
+++ b/src/segyio/segy.c
@@ -0,0 +1,1131 @@
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "segy.h"
+
+static unsigned char a2e[256] = {
+    0,  1,  2,  3,  55, 45, 46, 47, 22, 5,  37, 11, 12, 13, 14, 15,
+    16, 17, 18, 19, 60, 61, 50, 38, 24, 25, 63, 39, 28, 29, 30, 31,
+    64, 79, 127,123,91, 108,80, 125,77, 93, 92, 78, 107,96, 75, 97,
+    240,241,242,243,244,245,246,247,248,249,122,94, 76, 126,110,111,
+    124,193,194,195,196,197,198,199,200,201,209,210,211,212,213,214,
+    215,216,217,226,227,228,229,230,231,232,233,74, 224,90, 95, 109,
+    121,129,130,131,132,133,134,135,136,137,145,146,147,148,149,150,
+    151,152,153,162,163,164,165,166,167,168,169,192,106,208,161,7,
+    32, 33, 34, 35, 36, 21, 6,  23, 40, 41, 42, 43, 44, 9,  10, 27,
+    48, 49, 26, 51, 52, 53, 54, 8,  56, 57, 58, 59, 4,  20, 62, 225,
+    65, 66, 67, 68, 69, 70, 71, 72, 73, 81, 82, 83, 84, 85, 86, 87,
+    88, 89, 98, 99, 100,101,102,103,104,105,112,113,114,115,116,117,
+    118,119,120,128,138,139,140,141,142,143,144,154,155,156,157,158,
+    159,160,170,171,172,173,174,175,176,177,178,179,180,181,182,183,
+    184,185,186,187,188,189,190,191,202,203,204,205,206,207,218,219,
+    220,221,222,223,234,235,236,237,238,239,250,251,252,253,254,255
+};
+
+static unsigned char e2a[256] = {
+    0,  1,  2,  3,  156,9,  134,127,151,141,142, 11,12, 13, 14, 15,
+    16, 17, 18, 19, 157,133,8,  135,24, 25, 146,143,28, 29, 30, 31,
+    128,129,130,131,132,10, 23, 27, 136,137,138,139,140,5,  6,  7,
+    144,145,22, 147,148,149,150,4,  152,153,154,155,20, 21, 158,26,
+    32, 160,161,162,163,164,165,166,167,168,91, 46, 60, 40, 43, 33,
+    38, 169,170,171,172,173,174,175,176,177,93, 36, 42, 41, 59, 94,
+    45, 47, 178,179,180,181,182,183,184,185,124,44, 37, 95, 62, 63,
+    186,187,188,189,190,191,192,193,194,96, 58, 35, 64, 39, 61, 34,
+    195,97, 98, 99, 100,101,102,103,104,105,196,197,198,199,200,201,
+    202,106,107,108,109,110,111,112,113,114,203,204,205,206,207,208,
+    209,126,115,116,117,118,119,120,121,122,210,211,212,213,214,215,
+    216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,
+    123,65, 66, 67, 68, 69, 70, 71, 72, 73, 232,233,234,235,236,237,
+    125,74, 75, 76, 77, 78, 79, 80, 81, 82, 238,239,240,241,242,243,
+    92, 159,83, 84, 85, 86, 87, 88, 89, 90, 244,245,246,247,248,249,
+    48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 250,251,252,253,254,255
+};
+
+void ebcdic2ascii( const char* ebcdic, char* ascii ) {
+    while( *ebcdic != '\0' )
+        *ascii++ = e2a[ (unsigned char) *ebcdic++ ];
+
+    *ascii = '\0';
+}
+
+void ascii2ebcdic( const char* ascii, char* ebcdic ) {
+    while( *ascii != '\0' )
+        *ebcdic++ = a2e[ (unsigned char)*ascii++ ];
+
+    *ebcdic = '\0';
+}
+
+void ibm2ieee(void* to, const void* from, int len) {
+    register unsigned fr; /* fraction */
+    register int exp; /* exponent */
+    register int sgn; /* sign */
+
+    for (; len-- > 0; to = (char*) to + 4, from = (char*) from + 4) {
+        /* split into sign, exponent, and fraction */
+        fr = ntohl(*(int32_t*) from); /* pick up value */
+        sgn = fr >> 31; /* save sign */
+        fr <<= 1; /* shift sign out */
+        exp = fr >> 25; /* save exponent */
+        fr <<= 7; /* shift exponent out */
+
+        if (fr == 0) { /* short-circuit for zero */
+            exp = 0;
+            goto done;
+        }
+
+        /* adjust exponent from base 16 offset 64 radix point before first digit
+         * to base 2 offset 127 radix point after first digit
+         * (exp - 64) * 4 + 127 - 1 == exp * 4 - 256 + 126 == (exp << 2) - 130 */
+        exp = (exp << 2) - 130;
+
+        /* (re)normalize */
+        while (fr < 0x80000000) { /* 3 times max for normalized input */
+            --exp;
+            fr <<= 1;
+        }
+
+        if (exp <= 0) { /* underflow */
+            if (exp < -24) /* complete underflow - return properly signed zero */
+                fr = 0;
+            else /* partial underflow - return denormalized number */
+                fr >>= -exp;
+            exp = 0;
+        } else if (exp >= 255) { /* overflow - return infinity */
+            fr = 0;
+            exp = 255;
+        } else { /* just a plain old number - remove the assumed high bit */
+            fr <<= 1;
+        }
+
+        done:
+        /* put the pieces back together and return it */
+        *(unsigned*) to = (fr >> 9) | (exp << 23) | (sgn << 31);
+    }
+}
+
+void ieee2ibm(void* to, const void* from, int len) {
+    register unsigned fr; /* fraction */
+    register int exp; /* exponent */
+    register int sgn; /* sign */
+
+    for (; len-- > 0; to = (char*) to + 4, from = (char*) from + 4) {
+        /* split into sign, exponent, and fraction */
+        fr = *(unsigned*) from; /* pick up value */
+        sgn = fr >> 31; /* save sign */
+        fr <<= 1; /* shift sign out */
+        exp = fr >> 24; /* save exponent */
+        fr <<= 8; /* shift exponent out */
+
+        if (exp == 255) { /* infinity (or NAN) - map to largest */
+            fr = 0xffffff00;
+            exp = 0x7f;
+            goto done;
+        }
+        else if (exp > 0) /* add assumed digit */
+            fr = (fr >> 1) | 0x80000000;
+        else if (fr == 0) /* short-circuit for zero */
+            goto done;
+
+        /* adjust exponent from base 2 offset 127 radix point after first digit
+         * to base 16 offset 64 radix point before first digit */
+        exp += 130;
+        fr >>= -exp & 3;
+        exp = (exp + 3) >> 2;
+
+        /* (re)normalize */
+        while (fr < 0x10000000) { /* never executed for normalized input */
+            --exp;
+            fr <<= 4;
+        }
+
+        done:
+        /* put the pieces back together and return it */
+        fr = (fr >> 8) | (exp << 24) | (sgn << 31);
+        *(unsigned*) to = htonl(fr);
+    }
+}
+
+static void flipEndianness32(char* data, unsigned int count) {
+    for( unsigned int i = 0; i < count; i += 4) {
+        char a = data[i];
+        char b = data[i + 1];
+        data[i] = data[i + 3];
+        data[i + 1] = data[i + 2];
+        data[i + 2] = b;
+        data[i + 3] = a;
+    }
+}
+
+/* Lookup table for field sizes. All values not explicitly set are 0 */
+static int field_size[] = {
+    [CDP]                                     =  4,
+    [CDP_TRACE]                               =  4,
+    [CDP_X]                                   =  4,
+    [CDP_Y]                                   =  4,
+    [CROSSLINE_3D]                            =  4,
+    [EnergySourcePoint]                       =  4,
+    [FieldRecord]                             =  4,
+    [GroupWaterDepth]                         =  4,
+    [GroupX]                                  =  4,
+    [GroupY]                                  =  4,
+    [INLINE_3D]                               =  4,
+    [offset]                                  =  4,
+    [ReceiverDatumElevation]                  =  4,
+    [ReceiverGroupElevation]                  =  4,
+    [ShotPoint]                               =  4,
+    [SourceDatumElevation]                    =  4,
+    [SourceDepth]                             =  4,
+    [SourceEnergyDirectionMantissa]           =  4,
+    [SourceMeasurementExponent]               =  4,
+    [SourceSurfaceElevation]                  =  4,
+    [SourceWaterDepth]                        =  4,
+    [SourceX]                                 =  4,
+    [SourceY]                                 =  4,
+    [TraceNumber]                             =  4,
+    [TRACE_SEQUENCE_FILE]                     =  4,
+    [TRACE_SEQUENCE_LINE]                     =  4,
+    [TransductionConstantMantissa]            =  4,
+    [UnassignedInt1]                          =  4,
+    [UnassignedInt2]                          =  4,
+
+    [AliasFilterFrequency]                    =  2,
+    [AliasFilterSlope]                        =  2,
+    [CoordinateUnits]                         =  2,
+    [Correlated]                              =  2,
+    [DataUse]                                 =  2,
+    [DayOfYear]                               =  2,
+    [DelayRecordingTime]                      =  2,
+    [ElevationScalar]                         =  2,
+    [GainType]                                =  2,
+    [GapSize]                                 =  2,
+    [GeophoneGroupNumberFirstTraceOrigField]  =  2,
+    [GeophoneGroupNumberLastTraceOrigField]   =  2,
+    [GeophoneGroupNumberRoll1]                =  2,
+    [GroupStaticCorrection]                   =  2,
+    [GroupUpholeTime]                         =  2,
+    [HighCutFrequency]                        =  2,
+    [HighCutSlope]                            =  2,
+    [HourOfDay]                               =  2,
+    [InstrumentGainConstant]                  =  2,
+    [InstrumentInitialGain]                   =  2,
+    [LagTimeA]                                =  2,
+    [LagTimeB]                                =  2,
+    [LowCutFrequency]                         =  2,
+    [LowCutSlope]                             =  2,
+    [MinuteOfHour]                            =  2,
+    [MuteTimeEND]                             =  2,
+    [MuteTimeStart]                           =  2,
+    [NotchFilterFrequency]                    =  2,
+    [NotchFilterSlope]                        =  2,
+    [NStackedTraces]                          =  2,
+    [NSummedTraces]                           =  2,
+    [OverTravel]                              =  2,
+    [ScalarTraceHeader]                       =  2,
+    [SecondOfMinute]                          =  2,
+    [ShotPointScalar]                         =  2,
+    [SourceEnergyDirectionExponent]           =  2,
+    [SourceGroupScalar]                       =  2,
+    [SourceMeasurementMantissa]               =  2,
+    [SourceMeasurementUnit]                   =  2,
+    [SourceStaticCorrection]                  =  2,
+    [SourceType]                              =  2,
+    [SourceUpholeTime]                        =  2,
+    [SubWeatheringVelocity]                   =  2,
+    [SweepFrequencyEnd]                       =  2,
+    [SweepFrequencyStart]                     =  2,
+    [SweepLength]                             =  2,
+    [SweepTraceTaperLengthEnd]                =  2,
+    [SweepTraceTaperLengthStart]              =  2,
+    [SweepType]                               =  2,
+    [TaperType]                               =  2,
+    [TimeBaseCode]                            =  2,
+    [TotalStaticApplied]                      =  2,
+    [TraceIdentificationCode]                 =  2,
+    [TraceIdentifier]                         =  2,
+    [TRACE_SAMPLE_COUNT]                      =  2,
+    [TRACE_SAMPLE_INTERVAL]                   =  2,
+    [TraceValueMeasurementUnit]               =  2,
+    [TraceWeightingFactor]                    =  2,
+    [TransductionConstantPower]               =  2,
+    [TransductionUnit]                        =  2,
+    [WeatheringVelocity]                      =  2,
+    [YearDataRecorded]                        =  2,
+};
+
+#define HEADER_SIZE SEGY_TEXT_HEADER_SIZE
+
+/*
+ * Supporting same byte offsets as in the segy specification, i.e. from the
+ * start of the *text header*, not the binary header.
+ */
+static int bfield_size[] = {
+    [- HEADER_SIZE + BIN_JobID]                 =  4,
+    [- HEADER_SIZE + BIN_LineNumber]            =  4,
+    [- HEADER_SIZE + BIN_ReelNumber]            =  4,
+
+    [- HEADER_SIZE + BIN_Traces]                =  2,
+    [- HEADER_SIZE + BIN_AuxTraces]             =  2,
+    [- HEADER_SIZE + BIN_Interval]              =  2,
+    [- HEADER_SIZE + BIN_IntervalOriginal]      =  2,
+    [- HEADER_SIZE + BIN_Samples]               =  2,
+    [- HEADER_SIZE + BIN_SamplesOriginal]       =  2,
+    [- HEADER_SIZE + BIN_Format]                =  2,
+    [- HEADER_SIZE + BIN_EnsembleFold]          =  2,
+    [- HEADER_SIZE + BIN_SortingCode]           =  2,
+    [- HEADER_SIZE + BIN_VerticalSum]           =  2,
+    [- HEADER_SIZE + BIN_SweepFrequencyStart]   =  2,
+    [- HEADER_SIZE + BIN_SweepFrequencyEnd]     =  2,
+    [- HEADER_SIZE + BIN_SweepLength]           =  2,
+    [- HEADER_SIZE + BIN_Sweep]                 =  2,
+    [- HEADER_SIZE + BIN_SweepChannel]          =  2,
+    [- HEADER_SIZE + BIN_SweepTaperStart]       =  2,
+    [- HEADER_SIZE + BIN_SweepTaperEnd]         =  2,
+    [- HEADER_SIZE + BIN_Taper]                 =  2,
+    [- HEADER_SIZE + BIN_CorrelatedTraces]      =  2,
+    [- HEADER_SIZE + BIN_BinaryGainRecovery]    =  2,
+    [- HEADER_SIZE + BIN_AmplitudeRecovery]     =  2,
+    [- HEADER_SIZE + BIN_MeasurementSystem]     =  2,
+    [- HEADER_SIZE + BIN_ImpulseSignalPolarity] =  2,
+    [- HEADER_SIZE + BIN_VibratoryPolarity]     =  2,
+
+    [- HEADER_SIZE + BIN_Unassigned1]           =  0,
+
+    [- HEADER_SIZE + BIN_SEGYRevision]          =  2,
+    [- HEADER_SIZE + BIN_TraceFlag]             =  2,
+    [- HEADER_SIZE + BIN_ExtendedHeaders]       =  2,
+
+    [- HEADER_SIZE + BIN_Unassigned2]           =  0,
+};
+
+/*
+ * to/from_int32 are to be considered internal functions, but have external
+ * linkage so that tests can hook into them. They're not declared in the header
+ * files, and use of this internal interface is at user's own risk, i.e. it may
+ * change without notice.
+ */
+int to_int32( const char* buf ) {
+    int32_t value;
+    memcpy( &value, buf, sizeof( value ) );
+    return ((value >> 24) & 0xff)
+         | ((value << 8)  & 0xff0000)
+         | ((value >> 8)  & 0xff00)
+         | ((value << 24) & 0xff000000);
+}
+
+int to_int16( const char* buf ) {
+    int16_t value;
+    memcpy( &value, buf, sizeof( value ) );
+    return ((value >> 8) & 0x00ff)
+         | ((value << 8) & 0xff00);
+}
+
+/* from native int to segy int. fixed-width ints used as byte buffers */
+int32_t from_int32( int32_t buf ) {
+    int32_t value = 0;
+    memcpy( &value, &buf, sizeof( value ) );
+    return ((value >> 24) & 0xff)
+         | ((value << 8)  & 0xff0000)
+         | ((value >> 8)  & 0xff00)
+         | ((value << 24) & 0xff000000);
+}
+
+int16_t from_int16( int16_t buf ) {
+    int16_t value = 0;
+    memcpy( &value, &buf, sizeof( value ) );
+    return ((value >> 8) & 0x00ff)
+         | ((value << 8) & 0xff00);
+}
+
+static int get_field( const char* header,
+                      const int* table,
+                      int field,
+                      int* f ) {
+
+    const int bsize = table[ field ];
+
+    switch( bsize ) {
+        case 4:
+            *f = to_int32( header + (field - 1) );
+            return SEGY_OK;
+
+        case 2:
+            *f = to_int16( header + (field - 1) );
+            return SEGY_OK;
+
+        case 0:
+        default:
+            return SEGY_INVALID_FIELD;
+    }
+}
+
+int segy_get_field( const char* traceheader, int field, int* f ) {
+    if( field < 0 || field >= SEGY_TRACE_HEADER_SIZE )
+        return SEGY_INVALID_FIELD;
+
+    return get_field( traceheader, field_size, field, f );
+}
+
+int segy_get_bfield( const char* binheader, int field, int* f ) {
+    field -= SEGY_TEXT_HEADER_SIZE;
+
+    if( field < 0 || field >= SEGY_BINARY_HEADER_SIZE )
+        return SEGY_INVALID_FIELD;
+
+    return get_field( binheader, bfield_size, field, f );
+}
+
+static int set_field( char* header, const int* table, int field, int val ) {
+    const int bsize = table[ field ];
+
+    int32_t buf32;
+    int16_t buf16;
+
+    switch( bsize ) {
+        case 4:
+            buf32 = from_int32( val );
+            memcpy( header + (field - 1), &buf32, sizeof( buf32 ) );
+            return SEGY_OK;
+
+        case 2:
+            buf16 = from_int16( val );
+            memcpy( header + (field - 1), &buf16, sizeof( buf16 ) );
+            return SEGY_OK;
+
+        case 0:
+        default:
+            return SEGY_INVALID_FIELD;
+    }
+}
+
+int segy_set_field( char* traceheader, int field, int val ) {
+    if( field < 0 || field >= SEGY_TRACE_HEADER_SIZE )
+        return SEGY_INVALID_FIELD;
+
+    return set_field( traceheader, field_size, field, val );
+}
+
+int segy_set_bfield( char* binheader, int field, int val ) {
+    field -= SEGY_TEXT_HEADER_SIZE;
+
+    if( field < 0 || field >= SEGY_BINARY_HEADER_SIZE )
+        return SEGY_INVALID_FIELD;
+
+    return set_field( binheader, bfield_size, field, val );
+}
+
+int segy_binheader( FILE* fp, char* buf ) {
+    const int err = fseek( fp, SEGY_TEXT_HEADER_SIZE, SEEK_SET );
+    if( err != 0 ) return SEGY_FSEEK_ERROR;
+
+    const size_t read_count = fread( buf, 1, SEGY_BINARY_HEADER_SIZE, fp);
+    if( read_count != SEGY_BINARY_HEADER_SIZE )
+        return SEGY_FREAD_ERROR;
+
+    return SEGY_OK;
+}
+
+int segy_write_binheader( FILE* fp, const char* buf ) {
+    const int err = fseek( fp, SEGY_TEXT_HEADER_SIZE, SEEK_SET );
+    if( err != 0 ) return SEGY_FSEEK_ERROR;
+
+    const size_t writec = fwrite( buf, 1, SEGY_BINARY_HEADER_SIZE, fp);
+    if( writec != SEGY_BINARY_HEADER_SIZE )
+        return SEGY_FREAD_ERROR;
+
+    return SEGY_OK;
+}
+
+int segy_format( const char* buf ) {
+    int format;
+    segy_get_bfield( buf, BIN_Format, &format );
+    return format;
+}
+
+unsigned int segy_samples( const char* buf ) {
+    int samples;
+    segy_get_bfield( buf, BIN_Samples, &samples );
+    return samples;
+}
+
+unsigned int segy_trace_bsize( unsigned int samples ) {
+    /* Hard four-byte float assumption */
+    return samples * 4;
+}
+
+long segy_trace0( const char* binheader ) {
+    int extra_headers;
+    segy_get_bfield( binheader, BIN_ExtendedHeaders, &extra_headers );
+
+    return SEGY_TEXT_HEADER_SIZE + SEGY_BINARY_HEADER_SIZE +
+           SEGY_TEXT_HEADER_SIZE * extra_headers;
+}
+
+int segy_seek( FILE* fp,
+               unsigned int trace,
+               long trace0,
+               unsigned int trace_bsize ) {
+
+    trace_bsize += SEGY_TRACE_HEADER_SIZE;
+    const long pos = trace0 + ( (long)trace * (long)trace_bsize );
+    const int err = fseek( fp, pos, SEEK_SET );
+    if( err != 0 ) return SEGY_FSEEK_ERROR;
+    return SEGY_OK;
+}
+
+int segy_traceheader( FILE* fp,
+                      unsigned int traceno,
+                      char* buf,
+                      long trace0,
+                      unsigned int trace_bsize ) {
+
+    const int err = segy_seek( fp, traceno, trace0, trace_bsize );
+    if( err != 0 ) return err;
+
+    const size_t readc = fread( buf, 1, SEGY_TRACE_HEADER_SIZE, fp );
+
+    if( readc != SEGY_TRACE_HEADER_SIZE )
+        return SEGY_FREAD_ERROR;
+
+    return SEGY_OK;
+}
+
+int segy_write_traceheader( FILE* fp,
+                            unsigned int traceno,
+                            const char* buf,
+                            long trace0,
+                            unsigned int trace_bsize ) {
+
+    const int err = segy_seek( fp, traceno, trace0, trace_bsize );
+    if( err != 0 ) return err;
+
+    const size_t writec = fwrite( buf, 1, SEGY_TRACE_HEADER_SIZE, fp );
+
+    if( writec != SEGY_TRACE_HEADER_SIZE )
+        return SEGY_FWRITE_ERROR;
+
+    return SEGY_OK;
+}
+
+/*
+ * Determine the file size in bytes. If this function succeeds, the file
+ * pointer will be reset to wherever it was before this call. If this call
+ * fails for some reason, the return value is 0 and the file pointer location
+ * will be determined by the behaviour of fseek.
+ */
+static int file_size( FILE* fp, size_t* size ) {
+    const long prev_pos = ftell( fp );
+
+    int err = fseek( fp, 0, SEEK_END );
+    if( err != 0 ) return SEGY_FSEEK_ERROR;
+
+    const size_t sz = ftell( fp );
+    err = fseek( fp, prev_pos, SEEK_SET );
+    if( err != 0 ) return SEGY_FSEEK_ERROR;
+
+    *size = sz;
+    return SEGY_OK;
+}
+
+/*
+ * Return the number of traces in the file. The file pointer won't change after
+ * this call unless fseek itself fails.
+ *
+ * This function assumes that *all traces* are of the same size.
+ */
+int segy_traces( FILE* fp, size_t* traces, long trace0, unsigned int trace_bsize ) {
+
+    size_t fsize;
+    int err = file_size( fp, &fsize );
+    if( err != 0 ) return err;
+
+    trace_bsize += SEGY_TRACE_HEADER_SIZE;
+    const size_t trace_data_size = fsize - trace0;
+
+    if( trace_data_size % trace_bsize != 0 )
+        return SEGY_TRACE_SIZE_MISMATCH;
+
+    *traces = trace_data_size / trace_bsize;
+    return SEGY_OK;
+}
+
+static int segy_sample_interval(FILE* fp, double* dt) {
+
+    char bin_header[ SEGY_BINARY_HEADER_SIZE ];
+    char trace_header[SEGY_TRACE_HEADER_SIZE];
+
+    int err = segy_binheader( fp, bin_header );
+    if (err != 0) {
+        return err;
+    }
+
+    const long trace0 = segy_trace0( bin_header );
+    unsigned int samples = segy_samples( bin_header );
+    const size_t trace_bsize = segy_trace_bsize( samples );
+
+    err = segy_traceheader(fp, 0, trace_header, trace0, trace_bsize);
+    if (err != 0) {
+        return err;
+    }
+
+    int _binary_header_dt;
+    segy_get_bfield(bin_header, BIN_Interval, &_binary_header_dt);
+    double binary_header_dt = _binary_header_dt/1000.0;
+
+    int _trace_header_dt;
+    segy_get_field(trace_header, TRACE_SAMPLE_INTERVAL, &_trace_header_dt);
+    double trace_header_dt = _trace_header_dt/1000.0;
+
+
+    *dt = trace_header_dt;
+
+    if (binary_header_dt == 0.0 && trace_header_dt == 0.0) {
+        fprintf(stderr, "Trace sampling rate in SEGY header and trace header set to 0.0. Will default to 4 ms.\n");
+        *dt = 4.0;
+    } else if (binary_header_dt == 0.0) {
+        fprintf(stderr, "Trace sampling rate in SEGY header set to 0.0. Will use trace sampling rate of: %f\n", trace_header_dt);
+        *dt = trace_header_dt;
+    } else if (trace_header_dt == 0.0) {
+        fprintf(stderr, "Trace sampling rate in trace header set to 0.0. Will use SEGY header sampling rate of: %f\n", binary_header_dt);
+        *dt = binary_header_dt;
+    } else if (trace_header_dt != binary_header_dt) {
+        fprintf(stderr, "Trace sampling rate in SEGY header and trace header are not equal. Will use SEGY header sampling rate of: %f\n",
+                binary_header_dt);
+        *dt = binary_header_dt;
+    }
+
+    return 0;
+
+}
+
+int segy_sample_indexes(FILE* fp, double* buf, double t0, size_t count) {
+
+    double dt;
+    int err = segy_sample_interval(fp, &dt);
+    if (err != 0) {
+        return err;
+    }
+
+    for (int i = 0; i < count; i++) {
+        buf[i] = t0 + i * dt;
+    }
+
+    return 0;
+
+}
+
+/*
+ * Determine how a file is sorted. Expects the following two fields from the
+ * trace header to guide sorting: the inline number `il` and the crossline
+ * number `xl`.
+ *
+ * Inspects trace headers 0 and 1 and compares these two fields in the
+ * respective trace headers. If the first two traces are components of the same
+ * inline, header[0].ilnum should be equal to header[1].ilnum, similarly for
+ * crosslines. If neither match, the sorting is considered unknown.
+ */
+int segy_sorting( FILE* fp,
+                  int il,
+                  int xl,
+                  int* sorting,
+                  long trace0,
+                  unsigned int trace_bsize ) {
+    int err;
+    char traceheader[ SEGY_TRACE_HEADER_SIZE ];
+
+    err = segy_traceheader( fp, 0, traceheader, trace0, trace_bsize );
+    if( err != SEGY_OK ) return err;
+
+    /* make sure field is valid, so we don't have to check errors later */
+    if( field_size[ il ] == 0 || field_size[ xl ] == 0 )
+        return SEGY_INVALID_FIELD;
+
+    int il0, xl0, il1, xl1, off0, off1;
+
+    segy_get_field( traceheader, il, &il0 );
+    segy_get_field( traceheader, xl, &xl0 );
+    segy_get_field( traceheader, offset, &off0 );
+
+    int traceno = 1;
+
+    do {
+        err = segy_traceheader( fp, traceno, traceheader, trace0, trace_bsize );
+        if( err != SEGY_OK ) return err;
+
+        segy_get_field( traceheader, il, &il1 );
+        segy_get_field( traceheader, xl, &xl1 );
+        segy_get_field( traceheader, offset, &off1 );
+        ++traceno;
+    } while( off0 != off1 );
+
+    if     ( il0 == il1 ) *sorting = INLINE_SORTING;
+    else if( xl0 == xl1 ) *sorting = CROSSLINE_SORTING;
+    else return SEGY_INVALID_SORTING;
+
+    return SEGY_OK;
+}
+
+/*
+ * Find the number of offsets. This is determined by inspecting the trace
+ * headers [0,n) where n is the first trace where either the inline number or
+ * the crossline number changes (which changes first depends on sorting, but is
+ * irrelevant for this function).
+ */
+int segy_offsets( FILE* fp,
+                  int il,
+                  int xl,
+                  unsigned int traces,
+                  unsigned int* out,
+                  long trace0,
+                  unsigned int trace_bsize ) {
+    int err;
+    int il0, il1, xl0, xl1;
+    char header[ SEGY_TRACE_HEADER_SIZE ];
+    unsigned int offsets = 0;
+
+    do {
+        err = segy_traceheader( fp, offsets, header, trace0, trace_bsize );
+        if( err != 0 ) return err;
+
+        /*
+         * check that field value is sane, so that we don't have to check
+         * segy_get_field's error
+         */
+        if( field_size[ il ] == 0 || field_size[ xl ] == 0 )
+            return SEGY_INVALID_FIELD;
+
+        segy_get_field( header, il, &il0 );
+        segy_get_field( header, xl, &xl0 );
+
+        err = segy_traceheader( fp, offsets + 1, header, trace0, trace_bsize );
+        if( err != 0 ) return err;
+        segy_get_field( header, il, &il1 );
+        segy_get_field( header, xl, &xl1 );
+
+        ++offsets;
+    } while( il0 == il1 && xl0 == xl1 && offsets < traces );
+
+    if( offsets >= traces ) return SEGY_INVALID_OFFSETS;
+
+    *out = offsets;
+    return SEGY_OK;
+}
+
+int segy_line_indices( FILE* fp,
+                       int field,
+                       unsigned int traceno,
+                       unsigned int stride,
+                       unsigned int num_indices,
+                       unsigned int* buf,
+                       long trace0,
+                       unsigned int trace_bsize ) {
+
+    if( field_size[ field ] == 0 )
+        return SEGY_INVALID_FIELD;
+
+    char header[ SEGY_TRACE_HEADER_SIZE ];
+    for( ; num_indices--; traceno += stride, ++buf ) {
+
+        int err = segy_traceheader( fp, traceno, header, trace0, trace_bsize );
+        if( err != 0 ) return SEGY_FREAD_ERROR;
+
+        segy_get_field( header, field, (int*)buf );
+    }
+
+    return SEGY_OK;
+}
+
+static int count_lines( FILE* fp,
+                        int field,
+                        unsigned int offsets,
+                        unsigned int* out,
+                        long trace0,
+                        unsigned int trace_bsize ) {
+
+    int err;
+    char header[ SEGY_TRACE_HEADER_SIZE ];
+    err = segy_traceheader( fp, 0, header, trace0, trace_bsize );
+    if( err != 0 ) return err;
+
+    int first_lineno, first_offset, ln, off;
+
+    err = segy_get_field( header, field, &first_lineno );
+    if( err != 0 ) return err;
+
+    err = segy_get_field( header, 37, &first_offset );
+    if( err != 0 ) return err;
+
+    unsigned int lines = 1;
+    unsigned int curr = offsets;
+
+    while( true ) {
+        err = segy_traceheader( fp, curr, header, trace0, trace_bsize );
+        if( err != 0 ) return err;
+
+        segy_get_field( header, field, &ln );
+        segy_get_field( header, 37, &off );
+
+        if( first_offset == off && ln == first_lineno ) break;
+
+        curr += offsets;
+        ++lines;
+    }
+
+    *out = lines;
+    return SEGY_OK;
+}
+
+int segy_count_lines( FILE* fp,
+                      int field,
+                      unsigned int offsets,
+                      unsigned int* l1out,
+                      unsigned int* l2out,
+                      long trace0,
+                      unsigned int trace_bsize ) {
+
+    int err;
+    unsigned int l2count;
+    err = count_lines( fp, field, offsets, &l2count, trace0, trace_bsize );
+    if( err != 0 ) return err;
+
+    size_t traces;
+    err = segy_traces( fp, &traces, trace0, trace_bsize );
+    if( err != 0 ) return err;
+
+    const unsigned int line_length = l2count * offsets;
+    const unsigned int l1count = traces / line_length;
+
+    *l1out = l1count;
+    *l2out = l2count;
+
+    return SEGY_OK;
+}
+
+int segy_inline_length( int sorting,
+                        unsigned int traces,
+                        unsigned int crossline_count,
+                        unsigned int offsets,
+                        unsigned int* line_length ) {
+
+    if( sorting == INLINE_SORTING ) {
+        *line_length = crossline_count;
+        return SEGY_OK;
+    }
+
+    if( sorting == CROSSLINE_SORTING ) {
+        *line_length = traces / (crossline_count * offsets);
+        return SEGY_OK;
+    }
+
+    return SEGY_INVALID_SORTING;
+}
+
+int segy_crossline_length( int sorting,
+                           unsigned int traces,
+                           unsigned int inline_count,
+                           unsigned int offsets,
+                           unsigned int* line_length ) {
+
+    if( sorting == INLINE_SORTING ) {
+        *line_length = inline_count;
+        return SEGY_OK;
+    }
+
+    if( sorting == CROSSLINE_SORTING ) {
+        *line_length = traces / (inline_count * offsets);
+        return SEGY_OK;
+    }
+
+    return SEGY_INVALID_SORTING;
+}
+
+int segy_inline_indices( FILE* fp,
+                         int il,
+                         int sorting,
+                         unsigned int inline_count,
+                         unsigned int crossline_count,
+                         unsigned int offsets,
+                         unsigned int* buf,
+                         long trace0,
+                         unsigned int trace_bsize) {
+    int err;
+
+    if( sorting == INLINE_SORTING ) {
+        size_t traces;
+        err = segy_traces( fp, &traces, trace0, trace_bsize );
+        if( err != 0 ) return err;
+
+        unsigned int stride = crossline_count * offsets;
+        return segy_line_indices( fp, il, 0, stride, inline_count, buf, trace0, trace_bsize );
+    }
+
+    if( sorting == CROSSLINE_SORTING ) {
+        return segy_line_indices( fp, il, 0, offsets, inline_count, buf, trace0, trace_bsize );
+    }
+
+    return SEGY_INVALID_SORTING;
+}
+
+int segy_crossline_indices( FILE* fp,
+                            int xl,
+                            int sorting,
+                            unsigned int inline_count,
+                            unsigned int crossline_count,
+                            unsigned int offsets,
+                            unsigned int* buf,
+                            long trace0,
+                            unsigned int trace_bsize ) {
+
+    int err;
+
+    if( sorting == INLINE_SORTING ) {
+        return segy_line_indices( fp, xl, 0, offsets, crossline_count, buf, trace0, trace_bsize );
+    }
+
+    if( sorting == CROSSLINE_SORTING ) {
+        size_t traces;
+        err = segy_traces( fp, &traces, trace0, trace_bsize );
+        if( err != 0 ) return err;
+
+        unsigned int stride = inline_count * offsets;
+        return segy_line_indices( fp, xl, 0, stride, crossline_count, buf, trace0, trace_bsize );
+    }
+
+    return SEGY_INVALID_SORTING;
+}
+
+
+static int skip_traceheader( FILE* fp ) {
+    const int err = fseek( fp, SEGY_TRACE_HEADER_SIZE, SEEK_CUR );
+    if( err != 0 ) return SEGY_FSEEK_ERROR;
+    return SEGY_OK;
+}
+
+int segy_readtrace( FILE* fp,
+                    unsigned int traceno,
+                    float* buf,
+                    long trace0,
+                    unsigned int trace_bsize ) {
+    int err;
+    err = segy_seek( fp, traceno, trace0, trace_bsize );
+    if( err != 0 ) return err;
+
+    err = skip_traceheader( fp );
+    if( err != 0 ) return err;
+
+    const size_t readc = fread( buf, 1, trace_bsize, fp );
+    if( readc != trace_bsize ) return SEGY_FREAD_ERROR;
+
+    return SEGY_OK;
+
+}
+
+int segy_writetrace( FILE* fp,
+                     unsigned int traceno,
+                     const float* buf,
+                     long trace0,
+                     unsigned int trace_bsize ) {
+
+    int err;
+    err = segy_seek( fp, traceno, trace0, trace_bsize );
+    if( err != 0 ) return err;
+
+    err = skip_traceheader( fp );
+    if( err != 0 ) return err;
+
+    const size_t writec = fwrite( buf, 1, trace_bsize, fp );
+    if( writec != trace_bsize )
+        return SEGY_FWRITE_ERROR;
+    return SEGY_OK;
+}
+
+int segy_to_native( int format,
+                    unsigned int size,
+                    float* buf ) {
+
+    if( format == IEEE_FLOAT_4_BYTE )
+        flipEndianness32( (char*)buf, size * sizeof( float ) );
+    else
+        ibm2ieee( buf, buf, size );
+
+    return SEGY_OK;
+}
+
+int segy_from_native( int format,
+                      unsigned int size,
+                      float* buf ) {
+
+    if( format == IEEE_FLOAT_4_BYTE )
+        flipEndianness32( (char*)buf, size * sizeof( float ) );
+    else
+        ieee2ibm( buf, buf, size );
+
+    return SEGY_OK;
+}
+
+/*
+ * Determine the position of the element `x` in `xs`.
+ * Returns -1 if the value cannot be found
+ */
+static long index_of( unsigned int x,
+                      const unsigned int* xs,
+                      unsigned int sz ) {
+    for( unsigned int i = 0; i < sz; i++ ) {
+        if( xs[i] == x )
+            return i;
+    }
+
+    return -1;
+}
+
+/*
+ * Read the inline or crossline `lineno`. If it's an inline or crossline
+ * depends on the parameters. The line has a length of `line_length` traces,
+ * and `buf` must be of (minimum) `line_length*samples_per_trace` size.  Reads
+ * every `stride` trace, starting at the trace specified by the *position* of
+ * the value `lineno` in `linenos`. If `lineno` isn't present in `linenos`,
+ * SEGY_MISSING_LINE_INDEX will be returned.
+ *
+ * If reading a trace fails, this function will return whatever error
+ * segy_readtrace returns.
+ */
+int segy_read_line( FILE* fp,
+                    unsigned int line_trace0,
+                    unsigned int line_length,
+                    unsigned int stride,
+                    float* buf,
+                    long trace0,
+                    unsigned int trace_bsize ) {
+
+    const size_t trace_data_size = trace_bsize / 4;
+
+    for( ; line_length--; line_trace0 += stride, buf += trace_data_size ) {
+        int err = segy_readtrace( fp, line_trace0, buf, trace0, trace_bsize );
+        if( err != 0 ) return err;
+    }
+
+    return SEGY_OK;
+}
+
+/*
+ * Write the inline or crossline `lineno`. If it's an inline or crossline
+ * depends on the parameters. The line has a length of `line_length` traces,
+ * and `buf` must be of (minimum) `line_length*samples_per_trace` size.  Reads
+ * every `stride` trace, starting at the trace specified by the *position* of
+ * the value `lineno` in `linenos`. If `lineno` isn't present in `linenos`,
+ * SEGY_MISSING_LINE_INDEX will be returned.
+ *
+ * If reading a trace fails, this function will return whatever error
+ * segy_readtrace returns.
+ */
+int segy_write_line( FILE* fp,
+                     unsigned int line_trace0,
+                     unsigned int line_length,
+                     unsigned int stride,
+                     float* buf,
+                     long trace0,
+                     unsigned int trace_bsize ) {
+
+    const size_t trace_data_size = trace_bsize / 4;
+
+    for( ; line_length--; line_trace0 += stride, buf += trace_data_size ) {
+        int err = segy_writetrace( fp, line_trace0, buf, trace0, trace_bsize );
+        if( err != 0 ) return err;
+    }
+
+    return SEGY_OK;
+}
+
+
+int segy_line_trace0( unsigned int lineno,
+                      unsigned int line_length,
+                      unsigned int stride,
+                      const unsigned int* linenos,
+                      const unsigned int linenos_sz,
+                      unsigned int* traceno ) {
+
+    long index = index_of( lineno, linenos, linenos_sz );
+
+    if( index < 0 ) return SEGY_MISSING_LINE_INDEX;
+
+    if( stride == 1 ) index *= line_length;
+
+    *traceno = index;
+
+    return SEGY_OK;
+}
+
+int segy_inline_stride( int sorting,
+                        unsigned int inline_count,
+                        unsigned int* stride ) {
+    switch( sorting ) {
+        case CROSSLINE_SORTING:
+            *stride = inline_count;
+            return SEGY_OK;
+
+        case INLINE_SORTING:
+            *stride = 1;
+            return SEGY_OK;
+
+        default:
+            return SEGY_INVALID_SORTING;
+    }
+}
+
+int segy_crossline_stride( int sorting,
+                           unsigned int crossline_count,
+                           unsigned int* stride ) {
+    switch( sorting ) {
+        case CROSSLINE_SORTING:
+            *stride = 1;
+            return SEGY_OK;
+
+        case INLINE_SORTING:
+            *stride = crossline_count;
+            return SEGY_OK;
+
+        default:
+            return SEGY_INVALID_SORTING;
+    }
+}
+
+int segy_textheader( FILE* fp, char* buf ) {
+    rewind( fp );
+
+    const size_t read = fread( buf, 1, SEGY_TEXT_HEADER_SIZE, fp );
+    if( read != SEGY_TEXT_HEADER_SIZE ) return SEGY_FREAD_ERROR;
+
+    buf[ SEGY_TEXT_HEADER_SIZE ] = '\0';
+    ebcdic2ascii( buf, buf );
+    return SEGY_OK;
+}
+
+int segy_write_textheader( FILE* fp, unsigned int pos, const char* buf ) {
+    int err;
+    char mbuf[ SEGY_TEXT_HEADER_SIZE + 1 ];
+
+    // TODO: reconsider API, allow non-zero terminated strings
+    ascii2ebcdic( buf, mbuf );
+
+    const long offset = pos == 0
+                      ? 0
+                      : SEGY_TEXT_HEADER_SIZE + SEGY_BINARY_HEADER_SIZE +
+                        ((pos-1) * SEGY_TEXT_HEADER_SIZE);
+
+    err = fseek( fp, offset, SEEK_SET );
+    if( err != 0 ) return SEGY_FSEEK_ERROR;
+
+    size_t writec = fwrite( mbuf, 1, SEGY_TEXT_HEADER_SIZE, fp );
+    if( writec != SEGY_TEXT_HEADER_SIZE )
+        return SEGY_FWRITE_ERROR;
+
+    return SEGY_OK;
+}
+
+unsigned int segy_textheader_size() {
+    return SEGY_TEXT_HEADER_SIZE + 1;
+}
+
+unsigned int segy_binheader_size() {
+    return SEGY_BINARY_HEADER_SIZE;
+}
diff --git a/src/segyio/segy.h b/src/segyio/segy.h
new file mode 100644
index 0000000..17b3388
--- /dev/null
+++ b/src/segyio/segy.h
@@ -0,0 +1,393 @@
+#ifndef SEGYIO_SEGY_H
+#define SEGYIO_SEGY_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#define SEGY_BINARY_HEADER_SIZE 400
+#define SEGY_TEXT_HEADER_SIZE 3200
+#define SEGY_TRACE_HEADER_SIZE 240
+
+/*
+ * About signatures:
+ * If a function returns `int` you can assume the return value is an error
+ * code. 0 will always indicate success. If a function returns something else
+ * than an int it's typically an operation that cannot fail assuming the passed
+ * buffer is of the correct size. Any exceptions will be clearly stated.
+ *
+ * Function signatures are typically:
+ * 1) input parameters
+ * 2) output parameters
+ * 3) low-level file structure information
+ *
+ * Output parameters are non-const pointers, input parameters are const
+ * pointers or plain values. All functions are namespace-prefix'd with segy_.
+ * Some functions return values, notably the family concerned with the binary
+ * header such as segy_trace0, that should be used in consecutive segy function
+ * calls that use the same name for one of its parameters.
+ */
+
+/* binary header operations */
+/*
+ * The binheader buffer passed to these functions must be of *at least*
+ * `segy_binheader_size`.
+ */
+unsigned int segy_binheader_size();
+int segy_binheader( FILE*, char* buf );
+int segy_write_binheader( FILE*, const char* buf );
+unsigned int segy_samples( const char* binheader );
+/* exception: the int returned is an enum, SEGY_SORTING, not an error code */
+int segy_format( const char* binheader );
+int segy_get_field( const char* traceheader, int field, int* f );
+int segy_get_bfield( const char* binheader, int field, int* f );
+int segy_set_field( char* traceheader, int field, int val );
+int segy_set_bfield( char* binheader, int field, int val );
+
+unsigned segy_trace_bsize( unsigned int samples );
+/* byte-offset of the first trace header. */
+long segy_trace0( const char* binheader );
+/* number of traces in this file */
+int segy_traces( FILE*, size_t*, long trace0, unsigned int trace_bsize );
+
+int segy_sample_indexes(FILE* fp, double* buf, double t0, size_t count);
+
+/* text header operations */
+int segy_textheader( FILE*, char* buf );
+unsigned int segy_textheader_size();
+int segy_write_textheader( FILE*, unsigned int pos, const char* buf );
+
+/* Read the trace header at `traceno` into `buf`. */
+int segy_traceheader( FILE*,
+                      unsigned int traceno,
+                      char* buf,
+                      long trace0,
+                      unsigned int trace_bsize );
+
+/* Read the trace header at `traceno` into `buf`. */
+int segy_write_traceheader( FILE*,
+                            unsigned int traceno,
+                            const char* buf,
+                            long trace0,
+                            unsigned int trace_bsize );
+
+/*
+ * Number of traces in this file. The sorting type will be written to `sorting`
+ * if the function can figure out how the file is sorted. If not, some error
+ * code will be returned and `out` will not be modified.
+ */
+int segy_sorting( FILE*,
+                  int il,
+                  int xl,
+                  int* sorting,
+                  long trace0,
+                  unsigned int trace_bsize );
+
+/*
+ * Number of offsets in this file, written to `offsets`. 1 if a 3D data set, >1
+ * if a 4D data set.
+ */
+int segy_offsets( FILE*,
+                  int il,
+                  int xl,
+                  unsigned int traces,
+                  unsigned int* offsets,
+                  long trace0,
+                  unsigned int trace_bsize );
+
+/*
+ * read/write traces. Does not manipulate the buffers at all, i.e. in order to
+ * make sense of the read trace it must be converted to native floats, and the
+ * buffer sent to write must be converted to target float.
+ */
+int segy_readtrace( FILE*,
+                    unsigned int traceno,
+                    float* buf,
+                    long trace0,
+                    unsigned int trace_bsize );
+
+int segy_writetrace( FILE*,
+                     unsigned int traceno,
+                     const float* buf,
+                     long trace0,
+                     unsigned int trace_bsize );
+
+/* convert to/from native float from segy formats (likely IBM or IEEE) */
+int segy_to_native( int format,
+                    unsigned int size,
+                    float* buf );
+
+int segy_from_native( int format,
+                      unsigned int size,
+                      float* buf );
+
+int segy_read_line( FILE* fp,
+                    unsigned int line_trace0,
+                    unsigned int line_length,
+                    unsigned int stride,
+                    float* buf,
+                    long trace0,
+                    unsigned int trace_bsize );
+
+int segy_write_line( FILE* fp,
+                     unsigned int line_trace0,
+                     unsigned int line_length,
+                     unsigned int stride,
+                     float* buf,
+                     long trace0,
+                     unsigned int trace_bsize );
+
+/*
+ * Count inlines and crosslines. Use this function to determine how large buffer
+ * the functions `segy_inline_indices` and `segy_crossline_indices` expect.  If
+ * the file is sorted on inlines, `field` should the trace header field for the
+ * crossline number, and the inline number if the file is sorted on crosslines.
+ * If the file is sorted on inlines, `l1out` will contain the number of
+ * inlines, and `l2out` crosslines, and the other way around if the file is
+ * sorted on crosslines.
+ *
+ * `offsets` is the number of offsets in the file and be found with
+ * `segy_offsets`.
+ */
+int segy_count_lines( FILE*,
+                      int field,
+                      unsigned int offsets,
+                      unsigned int* l1out,
+                      unsigned int* l2out,
+                      long trace0,
+                      unsigned int trace_bsize );
+/*
+ * Find the `line_length` for the inlines. Assumes all inlines, crosslines and
+ * traces don't vary in length. `offsets` can be found with `segy_offsets`, and
+ * `traces` can be found with `segy_traces`.
+ *
+ * `inline_count` and `crossline_count` are the two values obtained with
+ * `segy_count_lines`.
+ */
+int segy_inline_length( int sorting,
+                        unsigned int traces,
+                        unsigned int crossline_count,
+                        unsigned int offsets,
+                        unsigned int* line_length );
+
+int segy_crossline_length( int sorting,
+                           unsigned int traces,
+                           unsigned int inline_count,
+                           unsigned int offsets,
+                           unsigned int* line_length );
+
+/*
+ * Find the indices of the inlines and write to `buf`. `offsets` are the number
+ * of offsets for this file as returned by `segy_offsets`
+ */
+int segy_inline_indices( FILE*,
+                         int il,
+                         int sorting,
+                         unsigned int inline_count,
+                         unsigned int crossline_count,
+                         unsigned int offsets,
+                         unsigned int* buf,
+                         long trace0,
+                         unsigned int trace_bsize );
+
+int segy_crossline_indices( FILE*,
+                            int xl,
+                            int sorting,
+                            unsigned int inline_count,
+                            unsigned int crossline_count,
+                            unsigned int offsets,
+                            unsigned int* buf,
+                            long trace0,
+                            unsigned int trace_bsize );
+
+/*
+ * Find the first `traceno` of the line `lineno`. `linenos` should be the line
+ * indices returned by `segy_inline_indices` or `segy_crossline_indices`. The
+ * stride depends on the sorting and is given by `segy_inline_stride` or
+ * `segy_crossline_stride`. `line_length` is the length, i.e. traces per line,
+ * given by `segy_inline_length` or `segy_crossline_length`.
+ *
+ * To read/write an inline, read `line_length` starting at `traceno`,
+ * incrementing `traceno` with `stride` `line_length` times.
+ */
+int segy_line_trace0( unsigned int lineno,
+                      unsigned int line_length,
+                      unsigned int stride,
+                      const unsigned int* linenos,
+                      const unsigned int linenos_sz,
+                      unsigned int* traceno );
+
+/*
+ * Find the stride needed for an inline/crossline traversal.
+ */
+int segy_inline_stride( int sorting,
+                        unsigned int inline_count,
+                        unsigned int* stride );
+
+int segy_crossline_stride( int sorting,
+                           unsigned int crossline_count,
+                           unsigned int* stride );
+
+typedef enum {
+    TRACE_SEQUENCE_LINE = 1,
+    TRACE_SEQUENCE_FILE = 5,
+    FieldRecord = 9,
+    TraceNumber = 13,
+    EnergySourcePoint = 17,
+    CDP = 21,
+    CDP_TRACE = 25,
+    TraceIdentificationCode = 29,
+    NSummedTraces = 31,
+    NStackedTraces = 33,
+    DataUse = 35,
+    offset = 37,
+    ReceiverGroupElevation = 41,
+    SourceSurfaceElevation = 45,
+    SourceDepth = 49,
+    ReceiverDatumElevation = 53,
+    SourceDatumElevation = 57,
+    SourceWaterDepth = 61,
+    GroupWaterDepth = 65,
+    ElevationScalar = 69,
+    SourceGroupScalar = 71,
+    SourceX = 73,
+    SourceY = 77,
+    GroupX = 81,
+    GroupY = 85,
+    CoordinateUnits = 89,
+    WeatheringVelocity = 91,
+    SubWeatheringVelocity = 93,
+    SourceUpholeTime = 95,
+    GroupUpholeTime = 97,
+    SourceStaticCorrection = 99,
+    GroupStaticCorrection = 101,
+    TotalStaticApplied = 103,
+    LagTimeA = 105,
+    LagTimeB = 107,
+    DelayRecordingTime = 109,
+    MuteTimeStart = 111,
+    MuteTimeEND = 113,
+    TRACE_SAMPLE_COUNT = 115,
+    TRACE_SAMPLE_INTERVAL = 117,
+    GainType = 119,
+    InstrumentGainConstant = 121,
+    InstrumentInitialGain = 123,
+    Correlated = 125,
+    SweepFrequencyStart = 127,
+    SweepFrequencyEnd = 129,
+    SweepLength = 131,
+    SweepType = 133,
+    SweepTraceTaperLengthStart = 135,
+    SweepTraceTaperLengthEnd = 137,
+    TaperType = 139,
+    AliasFilterFrequency = 141,
+    AliasFilterSlope = 143,
+    NotchFilterFrequency = 145,
+    NotchFilterSlope = 147,
+    LowCutFrequency = 149,
+    HighCutFrequency = 151,
+    LowCutSlope = 153,
+    HighCutSlope = 155,
+    YearDataRecorded = 157,
+    DayOfYear = 159,
+    HourOfDay = 161,
+    MinuteOfHour = 163,
+    SecondOfMinute = 165,
+    TimeBaseCode = 167,
+    TraceWeightingFactor = 169,
+    GeophoneGroupNumberRoll1 = 171,
+    GeophoneGroupNumberFirstTraceOrigField = 173,
+    GeophoneGroupNumberLastTraceOrigField = 175,
+    GapSize = 177,
+    OverTravel = 179,
+    CDP_X = 181,
+    CDP_Y = 185,
+    INLINE_3D = 189,
+    CROSSLINE_3D = 193,
+    ShotPoint = 197,
+    ShotPointScalar = 201,
+    TraceValueMeasurementUnit = 203,
+    TransductionConstantMantissa = 205,
+    TransductionConstantPower = 209,
+    TransductionUnit = 211,
+    TraceIdentifier = 213,
+    ScalarTraceHeader = 215,
+    SourceType = 217,
+    SourceEnergyDirectionMantissa = 219,
+    SourceEnergyDirectionExponent = 223,
+    SourceMeasurementMantissa = 225,
+    SourceMeasurementExponent = 229,
+    SourceMeasurementUnit = 231,
+    UnassignedInt1 = 233,
+    UnassignedInt2 = 237
+    
+} SEGY_FIELD;
+
+typedef enum {
+    BIN_JobID = 3201,
+    BIN_LineNumber = 3205,
+    BIN_ReelNumber = 3209,
+    BIN_Traces = 3213,
+    BIN_AuxTraces = 3215,
+    BIN_Interval = 3217,
+    BIN_IntervalOriginal = 3219,
+    BIN_Samples = 3221,
+    BIN_SamplesOriginal = 3223,
+    BIN_Format = 3225,
+    BIN_EnsembleFold = 3227,
+    BIN_SortingCode = 3229,
+    BIN_VerticalSum = 3231,
+    BIN_SweepFrequencyStart = 3233,
+    BIN_SweepFrequencyEnd = 3235,
+    BIN_SweepLength = 3237,
+    BIN_Sweep = 3239,
+    BIN_SweepChannel = 3241,
+    BIN_SweepTaperStart = 3243,
+    BIN_SweepTaperEnd = 3245,
+    BIN_Taper = 3247,
+    BIN_CorrelatedTraces = 3249,
+    BIN_BinaryGainRecovery = 3251,
+    BIN_AmplitudeRecovery = 3253,
+    BIN_MeasurementSystem = 3255,
+    BIN_ImpulseSignalPolarity = 3257,
+    BIN_VibratoryPolarity = 3259,
+    BIN_Unassigned1 = 3261,
+    BIN_SEGYRevision = 3501,
+    BIN_TraceFlag = 3503,
+    BIN_ExtendedHeaders = 3505,
+    BIN_Unassigned2 = 3507,
+} SEGY_BINFIELD;
+
+typedef enum {
+    IBM_FLOAT_4_BYTE = 1,
+    SIGNED_INTEGER_4_BYTE = 2,
+    SIGNED_SHORT_2_BYTE = 3,
+    FIXED_POINT_WITH_GAIN_4_BYTE = 4, // Obsolete
+    IEEE_FLOAT_4_BYTE = 5,
+    NOT_IN_USE_1 = 6,
+    NOT_IN_USE_2 = 7,
+    SIGNED_CHAR_1_BYTE = 8
+} SEGY_FORMAT;
+
+typedef enum {
+    UNKNOWN_SORTING = 0,
+    CROSSLINE_SORTING = 1,
+    INLINE_SORTING = 2,
+
+} SEGY_SORTING;
+
+typedef enum {
+    SEGY_OK = 0,
+    SEGY_FOPEN_ERROR,
+    SEGY_FSEEK_ERROR,
+    SEGY_FREAD_ERROR,
+    SEGY_FWRITE_ERROR,
+    SEGY_INVALID_FIELD,
+    SEGY_INVALID_SORTING,
+    SEGY_MISSING_LINE_INDEX,
+    SEGY_INVALID_OFFSETS,
+    SEGY_TRACE_SIZE_MISMATCH,
+} SEGY_ERROR;
+
+
+#endif //SEGYIO_SEGY_H
diff --git a/src/spec/segyspec.c b/src/spec/segyspec.c
new file mode 100644
index 0000000..a22b373
--- /dev/null
+++ b/src/spec/segyspec.c
@@ -0,0 +1,129 @@
+#include <malloc.h>
+
+#include "segyio/segy.h"
+#include "segyspec.h"
+
+static char* copyString(const char* path) {
+    size_t size = strlen(path) + 1;
+    char* path_copy = malloc(size);
+    memcpy(path_copy, path, size);
+    return path_copy;
+}
+
+
+int segyCreateSpec(SegySpec* spec, const char* file, unsigned int inline_field, unsigned int crossline_field, double t0) {
+
+    int errc = 0;
+
+    FILE* fp = fopen( file, "r" );
+    if (fp == NULL) {
+        fprintf(stderr, "Unable to open file: '%s'\n", file);
+        return -1;
+    }
+
+    spec->sample_indexes = NULL;
+    spec->inline_indexes = NULL;
+    spec->crossline_indexes = NULL;
+
+    char header[ SEGY_BINARY_HEADER_SIZE ];
+    errc = segy_binheader( fp, header );
+    if (errc!=0) {
+        goto CLEANUP;
+    }
+
+    spec->filename = copyString(file);
+    spec->sample_format = segy_format( header );
+    spec->sample_count = segy_samples( header );
+
+    spec->sample_indexes = malloc(sizeof(double) * spec->sample_count);
+    errc = segy_sample_indexes(fp, spec->sample_indexes, t0, spec->sample_count);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    const long trace0 = segy_trace0( header );
+
+    spec->trace_bsize = segy_trace_bsize( segy_samples( header ) );
+    size_t traces;
+    errc = segy_traces(fp, &traces, trace0, spec->trace_bsize);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    errc = segy_offsets(fp, inline_field, crossline_field, traces, &spec->offset_count, trace0, spec->trace_bsize);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    errc = segy_sorting(fp, inline_field, crossline_field, &spec->trace_sorting_format, trace0, spec->trace_bsize);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    unsigned int* l1;
+    unsigned int* l2;
+    unsigned int field;
+    if (spec->trace_sorting_format == INLINE_SORTING) {
+        field = crossline_field;
+        l1 = &spec->inline_count;
+        l2 = &spec->crossline_count;
+    } else if (spec->trace_sorting_format == CROSSLINE_SORTING) {
+        field = inline_field;
+        l2 = &spec->inline_count;
+        l1 = &spec->crossline_count;
+    } else {
+        fprintf(stderr, "Unknown sorting\n");
+        goto CLEANUP;
+    }
+
+    errc = segy_count_lines(fp, field, spec->offset_count, l1, l2, trace0, spec->trace_bsize);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    spec->inline_indexes = malloc(sizeof(unsigned int) * spec->inline_count);
+    spec->crossline_indexes = malloc(sizeof(unsigned int) * spec->crossline_count);
+
+    errc = segy_inline_indices(fp, inline_field, spec->trace_sorting_format,
+                        spec->inline_count, spec->crossline_count, spec->offset_count, spec->inline_indexes,
+                        trace0, spec->trace_bsize);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    errc = segy_crossline_indices(fp, crossline_field, spec->trace_sorting_format,
+                        spec->inline_count, spec->crossline_count, spec->offset_count, spec->crossline_indexes,
+                        trace0, spec->trace_bsize);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    spec->first_trace_pos = segy_trace0( header );
+
+    errc = segy_inline_stride(spec->trace_sorting_format, spec->inline_count, &spec->il_stride);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    errc = segy_crossline_stride(spec->trace_sorting_format, spec->crossline_count, &spec->xl_stride);
+    if (errc != 0) {
+        goto CLEANUP;
+    }
+
+    fclose(fp);
+
+    return 0;
+
+    CLEANUP:
+    if (spec->crossline_indexes != NULL)
+        free(spec->crossline_indexes);
+    if (spec->inline_indexes != NULL)
+        free(spec->inline_indexes);
+    if (spec->sample_indexes != NULL)
+        free(spec->sample_indexes);
+    free(spec->filename);
+
+    fclose(fp);
+
+    return errc;
+}
diff --git a/src/spec/segyspec.h b/src/spec/segyspec.h
new file mode 100644
index 0000000..80241f8
--- /dev/null
+++ b/src/spec/segyspec.h
@@ -0,0 +1,33 @@
+#ifndef SEGYIO_SEGYSPEC_H
+#define SEGYIO_SEGYSPEC_H
+
+#include <string.h>
+
+typedef struct {
+    char* filename;
+
+    unsigned int sample_format;
+
+    unsigned int* crossline_indexes;
+    unsigned int crossline_count;
+
+    unsigned int* inline_indexes;
+    unsigned int inline_count;
+
+    unsigned int offset_count;
+
+    double* sample_indexes;
+    unsigned int sample_count;
+
+    unsigned int trace_sorting_format;
+
+    unsigned int il_stride;
+    unsigned int xl_stride;
+    unsigned int first_trace_pos;
+    unsigned int trace_bsize;
+
+} SegySpec;
+
+int segyCreateSpec(SegySpec* spec, const char* file, unsigned int inline_field, unsigned int crossline_field, double t0);
+
+#endif //SEGYIO_SEGYSPEC_H
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..0a64837
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,14 @@
+configure_file(test-data/small.sgy test-data/small.sgy COPYONLY)
+configure_file(test-data/text.sgy  test-data/text.sgy COPYONLY)
+configure_file(test-data/small.sgy test-data/small-traceheader.sgy COPYONLY)
+
+add_segyio_test(utils test_utils.c)
+add_segyio_test(segy test_segy.c)
+add_segyio_test(segyspec test_segyspec.c)
+
+if(BUILD_MEX)
+    add_matlab_test(matlab.segyspec test_segyspec_mex.m test_segyspec_mex)
+    add_matlab_test(matlab.segy test_segy_mex.m test_segy_mex)
+endif()
+
+add_python_test(python.segy test_segy.py)
diff --git a/tests/test-data/small.sgy b/tests/test-data/small.sgy
new file mode 100644
index 0000000..bb659b6
Binary files /dev/null and b/tests/test-data/small.sgy differ
diff --git a/tests/test-data/text.sgy b/tests/test-data/text.sgy
new file mode 100644
index 0000000..d9e2926
Binary files /dev/null and b/tests/test-data/text.sgy differ
diff --git a/tests/test_segy.c b/tests/test_segy.c
new file mode 100644
index 0000000..23d1f1f
--- /dev/null
+++ b/tests/test_segy.c
@@ -0,0 +1,609 @@
+#include <math.h>
+#include <stdlib.h>
+
+#include <segyio/segy.h>
+
+#include "unittest.h"
+
+void test_interpret_file() {
+    /*
+     * test all structural properties of a file, i.e. traces, samples, sorting
+     * etc, but don't actually read any data
+     */
+    const char *file = "test-data/small.sgy";
+
+    int err;
+    char header[ SEGY_BINARY_HEADER_SIZE ];
+    int sorting;
+    size_t traces;
+    unsigned int inlines_sz, crosslines_sz;
+    unsigned int offsets, stride;
+    unsigned int line_trace0, line_length;
+    const int il = INLINE_3D;
+    const int xl = CROSSLINE_3D;
+
+    FILE* fp = fopen( file, "r" );
+
+    assertTrue( fp != NULL, "Could not open file." );
+    err = segy_binheader( fp, header );
+    assertTrue( err == 0, "Could not read binary header." );
+
+    const long trace0 = segy_trace0( header );
+    assertTrue( trace0 == 3600,
+                "Wrong byte offset of the first trace header. Expected 3600." );
+
+    const unsigned int samples = segy_samples( header );
+    assertTrue( samples == 50, "Expected 350 samples per trace." );
+
+    const size_t trace_bsize = segy_trace_bsize( samples );
+    assertTrue( trace_bsize == 50 * 4,
+                "Wrong trace byte size. Expected samples * 4-byte float." );
+
+    err = segy_traces( fp, &traces, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine number of traces in this file." );
+    assertTrue( traces == 25, "Expected 25 traces in the file." );
+
+    err = segy_sorting( fp, xl, il, &sorting, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not figure out file sorting." );
+    assertTrue( sorting == CROSSLINE_SORTING, "Expected crossline sorting." );
+
+    err = segy_sorting( fp, il, xl, &sorting, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not figure out file sorting." );
+    assertTrue( sorting == INLINE_SORTING, "Expected inline sorting." );
+
+    err = segy_offsets( fp, il, xl, traces, &offsets, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not figure out offsets." );
+    assertTrue( offsets == 1, "Expected offsets to be 1 (no extra offsets)." );
+
+    err = segy_count_lines( fp, xl, offsets, &inlines_sz, &crosslines_sz, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine line count in this file." );
+    assertTrue( inlines_sz == 5, "Expected 5 inlines." );
+    assertTrue( crosslines_sz == 5, "Expected 5 crosslines." );
+
+    /* Inline-specific information */
+    unsigned int inline_indices[ 5 ];
+    err = segy_inline_indices( fp, il, sorting, inlines_sz, crosslines_sz, offsets, inline_indices, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine inline linenos." );
+    for( unsigned int i = 0, ref = 1; i < 5; ++i, ++ref )
+        assertTrue( inline_indices[ i ] == ref,
+                    "Inline lineno mismatch, should be [1..5]." );
+
+    err = segy_inline_stride( sorting, inlines_sz, &stride );
+    assertTrue( err == 0, "Failure while reading stride." );
+    assertTrue( stride == 1, "Expected inline stride = 1." );
+
+    err = segy_line_trace0( 4, crosslines_sz, stride, inline_indices, inlines_sz, &line_trace0 );
+    assertTrue( err == 0, "Could not determine 2484's trace0." );
+    assertTrue( line_trace0 == 15, "Line 4 should start at traceno 15." );
+
+    err = segy_inline_length( sorting, traces, inlines_sz, offsets, &line_length );
+    assertTrue( err == 0, "Could not determine line length." );
+    assertTrue( line_length == 5, "Inline length should be 5." );
+
+    /* Crossline-specific information */
+    unsigned int crossline_indices[ 5 ];
+    err = segy_crossline_indices( fp, xl, sorting, inlines_sz, crosslines_sz, offsets, crossline_indices, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine crossline linenos." );
+    for( unsigned int i = 0, ref = 20; i < 5; ++i, ++ref )
+        assertTrue( crossline_indices[ i ] == ref,
+                    "Crossline lineno mismatch, should be [20..24]." );
+
+    err = segy_crossline_stride( sorting, crosslines_sz, &stride );
+    assertTrue( err == 0, "Failure while reading stride." );
+    assertTrue( stride == 5, "Expected crossline stride = 5." );
+
+    err = segy_line_trace0( 22, crosslines_sz, stride, crossline_indices, inlines_sz, &line_trace0 );
+    assertTrue( err == 0, "Could not determine 22's trace0." );
+    assertTrue( line_trace0 == 2, "Line 22 should start at traceno 2." );
+
+    err = segy_crossline_length( sorting, traces, inlines_sz, offsets, &line_length );
+    assertTrue( err == 0, "Failure finding length" );
+    assertTrue( line_length == 5, "Crossline length should be 5." );
+
+    fclose( fp );
+}
+
+/* Prestack test for when we have an approperiate prestack file
+
+void test_interpret_file_prestack() {
+    const char *file = "test-data/prestack.sgy";
+
+    int err;
+    char header[ SEGY_BINARY_HEADER_SIZE ];
+    int sorting;
+    size_t traces;
+    unsigned int inlines_sz, crosslines_sz;
+    unsigned int offsets, stride;
+    unsigned int line_trace0, line_length;
+    const int il = INLINE_3D;
+    const int xl = CROSSLINE_3D;
+
+    FILE* fp = fopen( file, "r" );
+
+    assertTrue( fp != NULL, "Could not open file." );
+    err = segy_binheader( fp, header );
+    assertTrue( err == 0, "Could not read binary header." );
+
+    const long trace0 = segy_trace0( header );
+    assertTrue( trace0 == 3600,
+                "Wrong byte offset of the first trace header. Expected 3600." );
+
+    const unsigned int samples = segy_samples( header );
+    assertTrue( samples == 326, "Expected 350 samples per trace." );
+
+    const size_t trace_bsize = segy_trace_bsize( samples );
+    assertTrue( trace_bsize == 326 * 4,
+                "Wrong trace byte size. Expected samples * 4-byte float." );
+
+    err = segy_traces( fp, &traces, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine number of traces in this file." );
+    assertTrue( traces == 16926, "Expected 286 traces in the file." );
+
+    err = segy_sorting( fp, xl, il, &sorting, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not figure out file sorting." );
+    assertTrue( sorting == CROSSLINE_SORTING, "Expected crossline sorting." );
+
+    err = segy_sorting( fp, il, xl, &sorting, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not figure out file sorting." );
+    assertTrue( sorting == INLINE_SORTING, "Expected inline sorting." );
+
+    err = segy_offsets( fp, il, xl, traces, &offsets, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not figure out offsets." );
+    assertTrue( offsets == 26, "Expected offsets to be 26." );
+
+    err = segy_count_lines( fp, xl, offsets, &inlines_sz, &crosslines_sz, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine line count in this file." );
+    assertTrue( inlines_sz == 31, "Expected 22 inlines." );
+    assertTrue( crosslines_sz == 21, "Expected 13 crosslines." );
+
+    unsigned int inline_indices[ 31 ];
+    err = segy_inline_indices( fp, il, sorting, inlines_sz, crosslines_sz, offsets, inline_indices, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine inline linenos." );
+    for( unsigned int i = 0, ref = 2500; i < 31; ++i, ++ref )
+        assertTrue( inline_indices[ i ] == ref,
+                    "Inline lineno mismatch, should be [2500..2530]." );
+
+    err = segy_inline_stride( sorting, inlines_sz, &stride );
+    assertTrue( err == 0, "Failure while reading stride." );
+    assertTrue( stride == 1, "Expected inline stride = 1." );
+
+    err = segy_line_trace0( 2504, crosslines_sz, stride, inline_indices, inlines_sz, &line_trace0 );
+    assertTrue( err == 0, "Could not determine 2504's trace0." );
+    assertTrue( line_trace0 == 84, "Line 2504 should start at traceno 84." );
+
+    err = segy_inline_length( sorting, traces, inlines_sz, offsets, &line_length );
+    assertTrue( err == 0, "Could not determine line length." );
+    assertTrue( line_length == 31, "Inline length should be 31." );
+
+    unsigned int crossline_indices[ 21 ];
+    err = segy_crossline_indices( fp, xl, sorting, inlines_sz, crosslines_sz, offsets, crossline_indices, trace0, trace_bsize );
+    assertTrue( err == 0, "Could not determine crossline linenos." );
+    for( unsigned int i = 0, ref = 2220; i < 21; ++i, ++ref )
+        assertTrue( crossline_indices[ i ] == ref,
+                    "Crossline lineno mismatch, should be [2220..2240]." );
+
+    err = segy_crossline_stride( sorting, crosslines_sz, &stride );
+    assertTrue( err == 0, "Failure while reading stride." );
+    assertTrue( stride == 21, "Expected crossline stride = 13." );
+
+    err = segy_line_trace0( 2222, crosslines_sz, stride, crossline_indices, inlines_sz, &line_trace0 );
+    assertTrue( err == 0, "Could not determine 2222's trace0." );
+    assertTrue( line_trace0 == 2, "Line 2222 should start at traceno 5." );
+
+    err = segy_crossline_length( sorting, traces, inlines_sz, offsets, &line_length );
+    assertTrue( err == 0, "Failure finding length" );
+    assertTrue( line_length == 31, "Crossline length should be 31." );
+
+    fclose( fp );
+}
+
+*/
+
+void testReadInLine_4(){
+    const char *file = "test-data/small.sgy";
+
+    int sorting;
+    size_t traces;
+    unsigned int inlines_sz, crosslines_sz;
+    unsigned int offsets, stride;
+    unsigned int line_trace0, line_length;
+
+    /* test specific consts */
+    const int il = INLINE_3D, xl = CROSSLINE_3D;
+
+    char header[ SEGY_BINARY_HEADER_SIZE ];
+
+    FILE* fp = fopen( file, "r" );
+    assertTrue( 0 == segy_binheader( fp, header ), "Could not read header" );
+    const long trace0 = segy_trace0( header );
+    const unsigned int samples = segy_samples( header );
+    const size_t trace_bsize = segy_trace_bsize( samples );
+    const int format = segy_format( header );
+
+    unsigned int inline_indices[ 5 ];
+    const unsigned int inline_length = 5;
+    float* data = malloc( inline_length * samples * sizeof( float ) );
+
+    int ok = 0;
+    ok = !segy_traces( fp, &traces, trace0, trace_bsize )
+      && !segy_sorting( fp, il, xl, &sorting, trace0, trace_bsize )
+      && !segy_offsets( fp, il, xl, traces, &offsets, trace0, trace_bsize )
+      && !segy_count_lines( fp, xl, offsets, &inlines_sz, &crosslines_sz, trace0, trace_bsize )
+      && !segy_inline_indices( fp, il, sorting, inlines_sz, crosslines_sz, offsets, inline_indices, trace0, trace_bsize )
+      && !segy_inline_stride( sorting, inlines_sz, &stride )
+      && !segy_line_trace0( 4, crosslines_sz, stride, inline_indices, inlines_sz, &line_trace0 )
+      && !segy_inline_length( sorting, traces, inlines_sz, offsets, &line_length )
+      && !segy_read_line( fp, line_trace0, line_length, stride, data, trace0, trace_bsize )
+      && !segy_to_native( format, inline_length * samples, data );
+
+    assertTrue( ok, "Error in setup. "
+                    "This integrity should be covered by other tests." );
+
+    //first xline
+    //first sample
+    assertClose(4.2f, data[0], 0.0001f);
+    //middle sample
+    assertClose(4.20024f, data[samples/2-1], 0.0001f);
+    //last sample
+    assertClose(4.20049f, data[samples-1], 0.0001f);
+
+    //middle xline
+    size_t middle_line = 2;
+    //first sample
+    assertClose(4.22f, data[samples*middle_line+0], 0.0001);
+    //middle sample
+    assertClose(4.22024f, data[samples*middle_line+samples/2-1], 0.0001);
+    //last sample
+    assertClose(4.22049f, data[samples*middle_line+samples-1], 0.0001);
+
+    //last xline
+    size_t last_line = (crosslines_sz-1);
+    //first sample
+    assertClose(4.24f, data[samples*last_line+0], 0);
+    //middle sample
+    assertClose(4.24024f, data[samples*last_line+samples/2-1], 0.0001);
+    //last sample
+    assertClose(4.24049f, data[samples*last_line+samples-1], 0.0001);
+
+    for( float* ptr = data; ptr < data + (samples * line_length); ++ptr )
+        assertTrue( *ptr >= 4.0f && *ptr <= 5.0f, "Sample value not in range." );
+
+    free(data);
+    fclose(fp);
+}
+
+void testReadCrossLine_22(){
+    const char *file = "test-data/small.sgy";
+
+    int sorting;
+    size_t traces;
+    unsigned int inlines_sz, crosslines_sz;
+    unsigned int offsets, stride;
+    unsigned int line_trace0, line_length;
+
+    /* test specific consts */
+    const int il = INLINE_3D, xl = CROSSLINE_3D;
+
+    char header[ SEGY_BINARY_HEADER_SIZE ];
+
+    FILE* fp = fopen( file, "r" );
+    assertTrue( 0 == segy_binheader( fp, header ), "Could not read header" );
+    const long trace0 = segy_trace0( header );
+    const unsigned int samples = segy_samples( header );
+    const size_t trace_bsize = segy_trace_bsize( samples );
+    const int format = segy_format( header );
+
+    unsigned int crossline_indices[ 5 ];
+    const unsigned int crossline_length = 5;
+    float* data = malloc( crossline_length * samples * sizeof(float) );
+
+    int ok = 0;
+    ok = !segy_traces( fp, &traces, trace0, trace_bsize )
+      && !segy_sorting( fp, il, xl, &sorting, trace0, trace_bsize )
+      && !segy_offsets( fp, il, xl, traces, &offsets, trace0, trace_bsize )
+      && !segy_count_lines( fp, xl, offsets, &inlines_sz, &crosslines_sz, trace0, trace_bsize )
+      && !segy_crossline_indices( fp, xl, sorting, inlines_sz, crosslines_sz, offsets, crossline_indices, trace0, trace_bsize )
+      && !segy_crossline_stride( sorting, crosslines_sz, &stride )
+      && !segy_line_trace0( 22, crosslines_sz, stride, crossline_indices, inlines_sz, &line_trace0 )
+      && !segy_crossline_length( sorting, traces, inlines_sz, offsets, &line_length )
+      && !segy_read_line( fp, line_trace0, line_length, stride, data, trace0, trace_bsize )
+      && !segy_to_native( format, crossline_length * samples, data );
+
+    assertTrue( ok, "Error in setup. "
+                    "This integrity should be covered by other tests." );
+
+    //first inline
+    //first sample
+    assertClose(1.22f, data[0], 0.0001);
+    //middle sample
+    assertClose(1.22024f, data[samples/2-1], 0.0001);
+    //last sample
+    assertClose(1.22049f, data[samples-1], 0.0001);
+
+    //middle inline
+    size_t middle_line = 2;
+    //first sample
+    assertClose(3.22f, data[samples*middle_line+0], 0.0001);
+    //middle sample
+    assertClose(3.22024f, data[samples*middle_line+samples/2-1], 0.0001);
+    //last sample
+    assertClose(3.22049f, data[samples*middle_line+samples-1], 0.0001);
+
+    //last inline
+    size_t last_line = (line_length-1);
+    //first sample
+    assertClose(5.22f, data[samples*last_line+0], 0.0001);
+    //middle sample
+    assertClose(5.22024f, data[samples*last_line+samples/2-1], 0.0001);
+    //last sample
+    assertClose(5.22049f, data[samples*last_line+samples-1], 0.0001);
+
+    for( float* ptr = data; ptr < data + (samples * line_length); ++ptr ) {
+        float xl = *ptr - floorf(*ptr);
+        assertTrue( 0.219f <= xl && xl <= 0.231f, "Sample value not in range" );
+    }
+
+    free(data);
+    fclose(fp);
+}
+
+int32_t from_int32( int32_t );
+
+void test_modify_trace_header() {
+    const char *file = "test-data/small-traceheader.sgy";
+
+    int err;
+    char bheader[ SEGY_BINARY_HEADER_SIZE ];
+
+    FILE* fp = fopen( file, "r+" );
+    err = segy_binheader( fp, bheader );
+    assertTrue( err == 0, "Could not read header" );
+    const long trace0 = segy_trace0( bheader );
+    const unsigned int samples = segy_samples( bheader );
+    const size_t trace_bsize = segy_trace_bsize( samples );
+
+    char traceh[ SEGY_TRACE_HEADER_SIZE ];
+    err = segy_traceheader( fp, 0, traceh, trace0, trace_bsize );
+    assertTrue( err == 0, "Error while reading trace header." );
+
+    int ilno;
+    err = segy_get_field( traceh, INLINE_3D, &ilno );
+    assertTrue( err == 0, "Error while reading iline no from field." );
+    assertTrue( ilno == 1, "Wrong iline no." );
+
+    err = segy_set_field( traceh, INLINE_3D, ilno + 1 );
+    assertTrue( err == 0, "Error writing to trace header buffer." );
+
+    err = segy_get_field( traceh, INLINE_3D, &ilno );
+    assertTrue( err == 0, "Error while reading iline no from dirty field." );
+    assertTrue( ilno == 2, "Wrong iline no." );
+
+    err = segy_write_traceheader( fp, 0, traceh, trace0, trace_bsize );
+    fflush( fp );
+
+    err = segy_traceheader( fp, 0, traceh, trace0, trace_bsize );
+    assertTrue( err == 0, "Error while reading trace header." );
+    err = segy_get_field( traceh, INLINE_3D, &ilno );
+    assertTrue( err == 0, "Error while reading iline no from dirty field." );
+    assertTrue( ilno == 2, "Wrong iline no." );
+
+    err = segy_set_field( traceh, INLINE_3D, 1 );
+    assertTrue( err == 0, "Error writing to trace header buffer." );
+    err = segy_write_traceheader( fp, 0, traceh, trace0, trace_bsize );
+    assertTrue( err == 0, "Error writing traceheader." );
+
+    fclose( fp );
+}
+
+static const char* expected_textheader =
+    "C 1 DATE: 22/02/2016                                                            "
+    "C 2 AN INCREASE IN AMPLITUDE EQUALS AN INCREASE IN ACOUSTIC IMPEDANCE           "
+    "C 3 FIRST SAMPLE: 4 MS, LAST SAMPLE: 1400 MS, SAMPLE INTERVAL: 4 MS             "
+    "C 4 DATA RANGE: INLINES=(2479-2500) (INC 1),CROSSLINES=(1428-1440) (INC 1)      "
+    "C 5 PROCESSING GRID CORNERS:                                                    "
+    "C 6 DISTANCE BETWEEN INLINES: 2499.75 M, CROSSLINES: 1250 M                     "
+    "C 7 1: INLINE 2479, CROSSLINE 1428, UTM-X 9976386.00, UTM-Y 9989096.00          "
+    "C 8 2: INLINE 2479, CROSSLINE 1440, UTM-X 9983886.00, UTM-Y 10002087.00         "
+    "C 9 3: INLINE 2500, CROSSLINE 1428, UTM-X 10021847.00, UTM-Y 9962849.00         "
+    "C10 4: INLINE 2500, CROSSLINE 1440, UTM-X 10029348.00, UTM-Y 9975839.00         "
+    "C11 TRACE HEADER POSITION:                                                      "
+    "C12   INLINE BYTES 005-008    | OFFSET BYTES 037-040                            "
+    "C13   CROSSLINE BYTES 021-024 | CMP UTM-X BYTES 181-184                         "
+    "C14   CMP UTM-Y BYTES 185-188                                                   "
+    "C15 END EBCDIC HEADER                                                           "
+    "C16                                                                             "
+    "C17                                                                             "
+    "C18                                                                             "
+    "C19                                                                             "
+    "C20                                                                             "
+    "C21                                                                             "
+    "C22                                                                             "
+    "C23                                                                             "
+    "C24                                                                             "
+    "C25                                                                             "
+    "C26                                                                             "
+    "C27                                                                             "
+    "C28                                                                             "
+    "C29                                                                             "
+    "C30                                                                             "
+    "C31                                                                             "
+    "C32                                                                             "
+    "C33                                                                             "
+    "C34                                                                             "
+    "C35                                                                             "
+    "C36                                                                             "
+    "C37                                                                             "
+    "C38                                                                             "
+    "C39                                                                             "
+    "C40                                                                            \x80";
+
+void test_text_header() {
+    const char *file = "test-data/text.sgy";
+    FILE* fp = fopen( file, "r" );
+
+    char ascii[ SEGY_TEXT_HEADER_SIZE + 1 ] = { 0 };
+    int err = segy_textheader( fp, ascii );
+    assertTrue( err == 0, "Could not read text header" );
+    assertTrue( strcmp(expected_textheader, ascii) == 0, "Text headers did not match" );
+
+    fclose( fp );
+}
+
+void test_trace_header_errors() {
+    const char *file = "test-data/small.sgy";
+    FILE* fp = fopen( file, "r" );
+    int err;
+
+    char binheader[ SEGY_BINARY_HEADER_SIZE ];
+    err = segy_binheader( fp, binheader );
+    assertTrue( err == 0, "Could not read binary header." );
+    const unsigned samples = segy_samples( binheader );
+    const unsigned bsize = segy_trace_bsize( samples );
+    const long trace0 = segy_trace0( binheader );
+
+    char header[ SEGY_TRACE_HEADER_SIZE ];
+
+    err = segy_traceheader( fp, 0, header, trace0, bsize );
+    assertTrue( err == 0, "Could not read trace header." );
+
+    /* reading outside header should yield invalid field */
+    int field;
+    err = segy_get_field( header, SEGY_TRACE_HEADER_SIZE + 10, &field );
+    assertTrue( err == SEGY_INVALID_FIELD, "Reading outside trace header." );
+
+    /* Reading between known byte offsets should yield error */
+    err = segy_get_field( header, INLINE_3D + 1, &field );
+    assertTrue( err == SEGY_INVALID_FIELD, "Reading between ok byte offsets." );
+
+    err = segy_get_field( header, INLINE_3D, &field );
+    assertTrue( err == SEGY_OK, "Reading failed at valid byte offset." );
+
+    err = segy_get_field( header, DayOfYear, &field );
+    assertTrue( err == SEGY_OK, "Reading failed at valid byte offset." );
+
+    fclose( fp );
+}
+
+void test_file_error_codes() {
+    const char *file = "test-data/small.sgy";
+    FILE* fp = fopen( file, "r" );
+    fclose( fp );
+
+    int err;
+    char binheader[ SEGY_BINARY_HEADER_SIZE ];
+    err = segy_binheader( fp, binheader );
+    assertTrue( err == SEGY_FSEEK_ERROR,
+                "Could read binary header from invalid file." );
+
+    const unsigned samples = segy_samples( binheader );
+    const unsigned trace_bsize = segy_trace_bsize( samples );
+    const long trace0 = segy_trace0( binheader );
+
+    char header[ SEGY_TRACE_HEADER_SIZE ];
+
+    err = segy_traceheader( fp, 0, header, trace0, trace_bsize );
+    assertTrue( err == SEGY_FSEEK_ERROR, "Could not read trace header." );
+
+    /* reading outside header should yield invalid field */
+    int field;
+    err = segy_get_field( header, SEGY_TRACE_HEADER_SIZE + 10, &field );
+    assertTrue( err == SEGY_INVALID_FIELD, "Reading outside trace header." );
+
+    /* Reading between known byte offsets should yield error */
+    err = segy_get_field( header, INLINE_3D + 1, &field );
+    assertTrue( err == SEGY_INVALID_FIELD, "Reading between ok byte offsets." );
+
+    err = segy_get_field( header, INLINE_3D, &field );
+    assertTrue( err == SEGY_OK, "Reading failed at valid byte offset." );
+
+    err = segy_get_field( header, DayOfYear, &field );
+    assertTrue( err == SEGY_OK, "Reading failed at valid byte offset." );
+
+    err = segy_textheader( fp, NULL );
+    assertTrue( err == SEGY_FSEEK_ERROR, "Could seek in invalid file." );
+
+    size_t traces;
+    err = segy_traces( fp, &traces, 3600, 350 );
+    assertTrue( err == SEGY_FSEEK_ERROR, "Could seek in invalid file." );
+
+    int sorting;
+    err = segy_sorting( fp, 0, 0, &sorting, 3600, 350 );
+    assertTrue( err == SEGY_FSEEK_ERROR, "Could seek in invalid file." );
+
+    err = segy_readtrace( fp, 0, NULL, 3600, 350 );
+    assertTrue( err == SEGY_FSEEK_ERROR, "Could seek in invalid file." );
+
+    err = segy_writetrace( fp, 0, NULL, 3600, 350 );
+    assertTrue( err == SEGY_FSEEK_ERROR, "Could seek in invalid file." );
+
+    unsigned int l1, l2;
+    err = segy_count_lines( fp, 0, 1, &l1, &l2, 3600, 350 );
+    assertTrue( err == SEGY_FSEEK_ERROR, "Could seek in invalid file." );
+}
+
+void test_error_codes_sans_file() {
+    int err;
+
+    unsigned int linenos[] = { 0, 1, 2 };
+    err = segy_line_trace0( 10, 3, 1, linenos, 3, NULL );
+    assertTrue( err == SEGY_MISSING_LINE_INDEX,
+                "Found line number that shouldn't exist." );
+
+    unsigned int stride;
+    err = segy_inline_stride( INLINE_SORTING + 3, 10, &stride );
+    assertTrue( err == SEGY_INVALID_SORTING,
+                "Expected sorting to be invalid." );
+
+    err = segy_crossline_stride( INLINE_SORTING + 3, 10, &stride );
+    assertTrue( err == SEGY_INVALID_SORTING,
+                "Expected sorting to be invalid." );
+
+    err = segy_inline_length( INLINE_SORTING + 3, 10, 10, 10, NULL );
+    assertTrue( err == SEGY_INVALID_SORTING,
+                "Expected sorting to be invalid." );
+
+    err = segy_crossline_length( INLINE_SORTING + 3, 10, 10, 10, NULL );
+    assertTrue( err == SEGY_INVALID_SORTING,
+                "Expected sorting to be invalid." );
+
+}
+
+
+/*
+ * segy_seek is private to the implementation, but we need external linkage for
+ * this test.
+ */
+int segy_seek( FILE*, unsigned int, long, unsigned int );
+
+void test_file_size_above_4GB(){
+    FILE* fp = tmpfile();
+
+    unsigned int trace = 5e6;
+    unsigned int trace_bsize = 1e3;
+    long trace0 = 0;
+    int err = segy_seek( fp, trace, trace0, trace_bsize);
+    assertTrue(err==0, "");
+    long pos = ftell(fp);
+    assertTrue(pos == (long)trace*((long)trace_bsize+SEGY_TRACE_HEADER_SIZE), "seek overflow");
+    fclose(fp);
+}
+
+int main() {
+    test_interpret_file();
+    /* test_interpret_file_prestack(); */
+    testReadInLine_4();
+    testReadCrossLine_22();
+    test_modify_trace_header();
+    test_text_header();
+    test_trace_header_errors();
+    /*
+     * due to its barely-defined behavorial nature, this test is commented out
+     * for most runs, as it would trip up the memcheck test
+     *
+     * test_file_error_codes();
+     */
+    test_error_codes_sans_file();
+
+    test_file_size_above_4GB();
+    exit(0);
+}
diff --git a/tests/test_segy.py b/tests/test_segy.py
new file mode 100644
index 0000000..96b2cb0
--- /dev/null
+++ b/tests/test_segy.py
@@ -0,0 +1,469 @@
+import numpy as np
+from unittest import TestCase
+import segyio
+from segyio import TraceField, BinField
+import shutil
+import sys
+import time
+import filecmp
+
+class TestSegy(TestCase):
+
+    def setUp(self):
+        self.filename = "test-data/small.sgy"
+
+    def test_inline_4(self):
+        with segyio.open(self.filename, "r") as f:
+
+            sample_count = f.samples
+            self.assertEqual(50, sample_count)
+
+            data = f.iline[4]
+
+            self.assertAlmostEqual(4.2, data[0, 0], places = 6)
+            # middle sample
+            self.assertAlmostEqual(4.20024, data[0, sample_count/2-1], places = 6)
+            # last sample
+            self.assertAlmostEqual(4.20049, data[0, -1], places = 6)
+
+            # middle xline
+            middle_line = 2
+            # first sample
+            self.assertAlmostEqual(4.22, data[middle_line, 0], places = 5)
+            # middle sample
+            self.assertAlmostEqual(4.22024, data[middle_line, sample_count/2-1], places = 6)
+            # last sample
+            self.assertAlmostEqual(4.22049, data[middle_line, -1], places = 6)
+
+            # last xline
+            last_line = (len(f.xlines)-1)
+            # first sample
+            self.assertAlmostEqual(4.24, data[last_line, 0], places = 5)
+            # middle sample
+            self.assertAlmostEqual(4.24024, data[last_line, sample_count/2-1], places = 6)
+            # last sample
+            self.assertAlmostEqual(4.24049, data[last_line, sample_count-1], places = 6)
+
+    def test_xline_22(self):
+        with segyio.open(self.filename, "r") as f:
+
+            data = f.xline[22]
+
+            # first iline
+            # first sample
+            self.assertAlmostEqual(1.22, data[0, 0], places = 5)
+            # middle sample
+            self.assertAlmostEqual(1.22024, data[0, f.samples/2-1], places = 6)
+            # last sample
+            self.assertAlmostEqual(1.22049, data[0, f.samples-1], places = 6)
+
+            # middle iline
+            middle_line = 2
+            # first sample
+            self.assertAlmostEqual(3.22, data[middle_line, 0], places = 5)
+            # middle sample
+            self.assertAlmostEqual(3.22024, data[middle_line, f.samples/2-1], places = 6)
+            # last sample
+            self.assertAlmostEqual(3.22049, data[middle_line, f.samples-1], places = 6)
+
+            # last iline
+            last_line = len(f.ilines)-1
+            # first sample
+            self.assertAlmostEqual(5.22, data[last_line, 0], places = 5)
+            # middle sample
+            self.assertAlmostEqual(5.22024, data[last_line, f.samples/2-1], places = 6)
+            # last sample
+            self.assertAlmostEqual(5.22049, data[last_line, f.samples-1], places = 6)
+
+    def test_iline_slicing(self):
+        with segyio.open(self.filename, "r") as f:
+            self.assertEqual(len(f.ilines), sum(1 for _ in f.iline))
+            self.assertEqual(len(f.ilines), sum(1 for _ in f.iline[1:6]))
+            self.assertEqual(len(f.ilines), sum(1 for _ in f.iline[5:0:-1]))
+            self.assertEqual(len(f.ilines) / 2, sum(1 for _ in f.iline[0::2]))
+            self.assertEqual(len(f.ilines), sum(1 for _ in f.iline[1:]))
+            self.assertEqual(3,  sum(1 for _ in f.iline[::2]))
+            self.assertEqual(0,  sum(1 for _ in f.iline[12:24]))
+            self.assertEqual(3,  sum(1 for _ in f.iline[:4]))
+            self.assertEqual(2,  sum(1 for _ in f.iline[2:6:2]))
+
+    def test_xline_slicing(self):
+        with segyio.open(self.filename, "r") as f:
+            self.assertEqual(len(f.xlines), sum(1 for _ in f.xline))
+            self.assertEqual(len(f.xlines), sum(1 for _ in f.xline[20:25]))
+            self.assertEqual(len(f.xlines), sum(1 for _ in f.xline[25:19:-1]))
+            self.assertEqual(3, sum(1 for _ in f.xline[0::2]))
+            self.assertEqual(3, sum(1 for _ in f.xline[::2]))
+            self.assertEqual(len(f.xlines), sum(1 for _ in f.xline[20:]))
+            self.assertEqual(0,  sum(1 for _ in f.xline[12:18]))
+            self.assertEqual(5,  sum(1 for _ in f.xline[:25]))
+            self.assertEqual(2,  sum(1 for _ in f.xline[:25:3]))
+
+    def test_open_transposed_lines(self):
+        il, xl = [], []
+        with segyio.open(self.filename, "r") as f:
+            il = f.ilines
+            xl = f.xlines
+
+        with segyio.open(self.filename, "r", segyio.TraceField.CROSSLINE_3D, segyio.TraceField.INLINE_3D) as f:
+            self.assertEqual(il, f.xlines)
+            self.assertEqual(xl, f.ilines)
+            pass
+
+
+    def test_file_info(self):
+        with segyio.open(self.filename, "r") as f:
+            self.assertEqual(2, f.sorting)
+            self.assertEqual(1, f.offsets)
+            self.assertEqual(1, int(f.format))
+
+            xlines = list(xrange(20, 25))
+            ilines = list(xrange(1, 6))
+            self.assertEqual(xlines, f.xlines)
+            self.assertEqual(ilines, f.ilines)
+            self.assertEqual(25, f.tracecount)
+            self.assertEqual(len(f.trace), f.tracecount)
+            self.assertEqual(50, f.samples)
+
+    def native_conversion(self):
+        arr1 = np.random.rand(10, dtype=np.float32)
+        arr2 = np.copy(arr1)
+        self.assertTrue(np.array_equal(arr1, arr2))
+
+        # round-trip should not modify data
+        segyio.file._from_native(1, f.samples, segyio.asfloatp(arr1))
+        self.assertFalse(np.array_equal(arr1, arr2))
+        segyio.file._to_native(1, f.samples, segyio.asfloatp(arr1))
+        self.assertTrue(np.array_equal(arr1, arr2))
+
+    def test_traces_slicing(self):
+        with segyio.open(self.filename, "r") as f:
+
+            traces = map(np.copy, f.trace[0:6:2])
+            self.assertEqual(len(traces), 3)
+            self.assertEqual(traces[0][49], f.trace[0][49])
+            self.assertEqual(traces[1][49], f.trace[2][49])
+            self.assertEqual(traces[2][49], f.trace[4][49])
+
+            rev_traces = map(np.copy, f.trace[4::-2])
+            self.assertEqual(rev_traces[0][49], f.trace[4][49])
+            self.assertEqual(rev_traces[1][49], f.trace[2][49])
+            self.assertEqual(rev_traces[2][49], f.trace[0][49])
+
+            # make sure buffers can be reused
+            buf = None
+            for i, trace in enumerate(f.trace[0:6:2, buf]):
+                self.assertTrue(np.array_equal(trace, traces[i]))
+
+    def test_line_generators(self):
+        with segyio.open(self.filename, "r") as f:
+            for line in f.iline:
+                pass
+
+            for line in f.xline:
+                pass
+
+    def test_read_header(self):
+        with segyio.open(self.filename, "r") as f:
+            self.assertEqual(1, f.header[0][189])
+            self.assertEqual(1, f.header[1][TraceField.INLINE_3D])
+
+            with self.assertRaises(IndexError):
+                f.header[0][188] # between byte offsets
+
+            with self.assertRaises(IndexError):
+                f.header[0][-1]
+
+            with self.assertRaises(IndexError):
+                f.header[0][700]
+
+    def test_write_header(self):
+        fname = self.filename + "write-header"
+        shutil.copyfile(self.filename, fname)
+        with segyio.open(fname, "r+") as f:
+            # assign to a field in a header, write immediately
+            f.header[0][189] = 42
+            f.flush()
+
+            self.assertEqual(42, f.header[0][189])
+            self.assertEqual(1, f.header[1][189])
+
+            # accessing non-existing offsets raises exceptions
+            with self.assertRaises(IndexError):
+                f.header[0][188] = 1 # between byte offsets
+
+            with self.assertRaises(IndexError):
+                f.header[0][-1] = 1
+
+            with self.assertRaises(IndexError):
+                f.header[0][700] = 1
+
+            d = { TraceField.INLINE_3D: 43,
+                  TraceField.CROSSLINE_3D: 11,
+                  TraceField.offset: 15 }
+
+            # assign multiple fields at once by using a dict
+            f.header[1] = d
+
+            f.flush()
+            self.assertEqual(43, f.header[1][TraceField.INLINE_3D])
+            self.assertEqual(11, f.header[1][TraceField.CROSSLINE_3D])
+            self.assertEqual(15, f.header[1][TraceField.offset])
+
+            # looking up multiple values at once returns a { TraceField: value } dict
+            self.assertEqual(d, f.header[1][TraceField.INLINE_3D, TraceField.CROSSLINE_3D, TraceField.offset])
+
+            # slice-support over headers (similar to trace)
+            for th in f.header[0:10]:
+                pass
+
+            self.assertEqual(6, len(list(f.header[10::-2])))
+            self.assertEqual(5, len(list(f.header[10:5:-1])))
+            self.assertEqual(0, len(list(f.header[10:5])))
+
+            # for-each support
+            for th in f.header:
+                pass
+
+            # copy a header
+            f.header[2] = f.header[1]
+            f.flush()
+            # don't use this interface in production code, it's only for testing
+            # i.e. don't access buf of treat it as a list
+            self.assertEqual(list(f.header[2].buf), list(f.header[1].buf))
+
+    def test_write_binary(self):
+        fname = self.filename.replace( ".sgy", "-binary.sgy")
+        shutil.copyfile(self.filename, fname)
+
+        with segyio.open(fname, "r+") as f:
+            f.bin[3213] = 5
+            f.flush()
+
+            self.assertEqual(5, f.bin[3213])
+
+            # accessing non-existing offsets raises exceptions
+            with self.assertRaises(IndexError):
+                f.bin[0]
+
+            with self.assertRaises(IndexError):
+                f.bin[50000]
+
+            with self.assertRaises(IndexError):
+                f.bin[3214]
+
+            d = { BinField.Traces: 43,
+                  BinField.SweepFrequencyStart: 11 }
+
+            # assign multiple fields at once by using a dict
+            f.bin = d
+
+            f.flush()
+            self.assertEqual(43, f.bin[BinField.Traces])
+            self.assertEqual(11, f.bin[BinField.SweepFrequencyStart])
+
+            # looking up multiple values at once returns a { TraceField: value } dict
+            self.assertEqual(d, f.bin[BinField.Traces, BinField.SweepFrequencyStart])
+
+            # copy a header
+            f.bin = f.bin
+
+    def test_fopen_error(self):
+        # non-existent file
+        with self.assertRaises(OSError):
+            segyio.open("no_dir/no_file", "r")
+
+        # non-existant mode
+        with self.assertRaises(OSError):
+            segyio.open(self.filename, "foo")
+
+    def test_wrong_lineno(self):
+        with self.assertRaises(KeyError):
+            with segyio.open(self.filename, "r") as f:
+                f.iline[3000]
+
+        with self.assertRaises(KeyError):
+            with segyio.open(self.filename, "r") as f:
+                f.xline[2]
+
+    def test_open_wrong_inline(self):
+        with self.assertRaises(ValueError):
+            with segyio.open(self.filename, "r", 2) as f:
+                pass
+
+    def test_open_wrong_crossline(self):
+        with self.assertRaises(ValueError):
+            with segyio.open(self.filename, "r", 189, 2) as f:
+                pass
+
+    def test_create_sgy(self):
+        dstfile = self.filename.replace(".sgy", "-created.sgy")
+
+        with segyio.open(self.filename, "r") as src:
+            spec = segyio.spec()
+            spec.format     = int(src.format)
+            spec.sorting    = int(src.sorting)
+            spec.samples    = src.samples
+            spec.ilines     = src.ilines
+            spec.xlines     = src.xlines
+
+            with segyio.create(dstfile, spec) as dst:
+                dst.text[0] = src.text[0]
+                dst.bin     = src.bin
+
+                # copy all headers
+                dst.header = src.header
+
+                for i, srctr in enumerate(src.trace):
+                    dst.trace[i] = srctr
+
+                dst.trace = src.trace
+
+                # this doesn't work yet, some restructuring is necessary
+                # if it turns out to be a desired feature it's rather easy to do
+                #for dsth, srch in zip(dst.header, src.header):
+                #    dsth = srch
+
+                #for dsttr, srctr in zip(dst.trace, src.trace):
+                #    dsttr = srctr
+
+        self.assertTrue(filecmp.cmp(self.filename, dstfile))
+
+    def test_create_sgy_shorter_traces(self):
+        dstfile = self.filename.replace(".sgy", "-shorter.sgy")
+
+        with segyio.open(self.filename, "r") as src:
+            spec = segyio.spec()
+            spec.format     = int(src.format)
+            spec.sorting    = int(src.sorting)
+            spec.samples    = 20 # reduces samples per trace
+            spec.ilines     = src.ilines
+            spec.xlines     = src.xlines
+
+            with segyio.create(dstfile, spec) as dst:
+                for i, srch in enumerate(src.header):
+                    dst.header[i] = srch
+                    d = { TraceField.INLINE_3D: srch[TraceField.INLINE_3D] + 100 }
+                    dst.header[i] = d
+
+                for lineno in dst.ilines:
+                    dst.iline[lineno] = src.iline[lineno]
+
+                # alternative form using left-hand-side slices
+                dst.iline[2:4] = src.iline
+
+
+                buf = None # reuse buffer for speed
+                for lineno in dst.xlines:
+                    dst.xline[lineno] = src.xline[lineno, buf]
+
+            with segyio.open(dstfile, "r") as dst:
+                self.assertEqual(20, dst.samples)
+                self.assertEqual([x + 100 for x in src.ilines], dst.ilines)
+
+    def test_create_from_naught(self):
+        fname = "test-data/mk.sgy"
+        spec = segyio.spec()
+        spec.format  = 5
+        spec.sorting = 2
+        spec.samples = 150
+        spec.ilines  = range(1, 11)
+        spec.xlines  = range(1, 6)
+
+        with segyio.create(fname, spec) as dst:
+            tr = np.arange( start = 1.000, stop = 1.151, step = 0.001, dtype = np.float32 )
+
+            for i in xrange( len( dst.trace ) ):
+                dst.trace[i] = tr
+                tr += 1.000
+
+            for il in spec.ilines:
+                dst.header.iline[il] = { TraceField.INLINE_3D: il }
+
+            for xl in spec.xlines:
+                dst.header.xline[xl] = { TraceField.CROSSLINE_3D: xl }
+
+            # Set header field 'offset' to 1 in all headers
+            dst.header = { TraceField.offset: 1 }
+
+        with segyio.open(fname, "r") as f:
+            self.assertAlmostEqual(1,      f.trace[0][0],   places = 4)
+            self.assertAlmostEqual(1.001,  f.trace[0][1],   places = 4)
+            self.assertAlmostEqual(1.149,  f.trace[0][-1],   places = 4)
+            self.assertAlmostEqual(50.100, f.trace[-1][100], places = 4)
+            self.assertEqual(f.header[0][TraceField.offset], f.header[1][TraceField.offset])
+            self.assertEqual(1, f.header[1][TraceField.offset])
+
+    @staticmethod
+    def mklines(fname):
+        spec = segyio.spec()
+        spec.format  = 5
+        spec.sorting = 2
+        spec.samples = 10
+        spec.ilines  = range(1, 11)
+        spec.xlines  = range(1, 6)
+
+# create a file with 10 inlines, with values on the form l.0tv where
+# l = line no
+# t = trace number (within line)
+# v = trace value
+# i.e. 2.043 is the value at inline 2's fourth trace's third value
+        with segyio.create(fname, spec) as dst:
+            ln = np.arange(start = 0,
+                           stop = 0.001 * (5 * 10),
+                           step = 0.001,
+                           dtype = np.float32).reshape(5, 10)
+
+            for il in spec.ilines:
+                ln += 1
+
+                dst.header.iline[il] = { TraceField.INLINE_3D: il }
+                dst.iline[il] = ln
+
+            for xl in spec.xlines:
+                dst.header.xline[xl] = { TraceField.CROSSLINE_3D: xl }
+
+    def test_create_write_lines(self):
+        fname = "test-data/mklines.sgy"
+
+        self.mklines(fname)
+
+        with segyio.open(fname, "r") as f:
+            self.assertAlmostEqual(1,     f.iline[1][0][0], places = 4)
+            self.assertAlmostEqual(2.004, f.iline[2][0][4], places = 4)
+            self.assertAlmostEqual(2.014, f.iline[2][1][4], places = 4)
+            self.assertAlmostEqual(8.043, f.iline[8][4][3], places = 4)
+
+    def test_create_sgy_skip_lines(self):
+        fname = "test-data/lines.sgy"
+        dstfile = fname.replace(".sgy", "-halved.sgy")
+
+        self.mklines(fname)
+
+        with segyio.open(fname, "r") as src:
+            spec = segyio.spec()
+            spec.format     = int(src.format)
+            spec.sorting    = int(src.sorting)
+            spec.samples    = src.samples
+            spec.ilines     = src.ilines[::2]
+            spec.xlines     = src.xlines[::2]
+
+            with segyio.create(dstfile, spec) as dst:
+                # use the inline headers as base
+                dst.header.iline = src.header.iline[::2]
+                # then update crossline numbers from the crossline headers
+                for xl in dst.xlines:
+                    f = next(src.header.xline[xl])[TraceField.CROSSLINE_3D]
+                    dst.header.xline[xl] = { TraceField.CROSSLINE_3D: f }
+
+                # but we override the last xline to be 6, not 5
+                dst.header.xline[5] = { TraceField.CROSSLINE_3D: 6 }
+                dst.iline = src.iline[::2]
+
+        with segyio.open(dstfile, "r") as f:
+            self.assertEqual(f.ilines, spec.ilines)
+            self.assertEqual(f.xlines, [1,3,6])
+            self.assertAlmostEqual(1,     f.iline[1][0][0], places = 4)
+            self.assertAlmostEqual(3.004, f.iline[3][0][4], places = 4)
+            self.assertAlmostEqual(3.014, f.iline[3][1][4], places = 4)
+            self.assertAlmostEqual(7.023, f.iline[7][2][3], places = 4)
diff --git a/tests/test_segy_mex.m b/tests/test_segy_mex.m
new file mode 100644
index 0000000..7b4dc15
--- /dev/null
+++ b/tests/test_segy_mex.m
@@ -0,0 +1,230 @@
+% test segyline
+
+% preconditions
+filename = 'test-data/small.sgy';
+assert(exist(filename,'file')==2);
+t0 = 1111.0;
+
+%% Spec is created
+try
+    spec = Segy.interpret_segycube(filename, 'INLINE_3D', 'CROSSLINE_3D', t0);
+    spec = Segy.interpret_segycube(filename, TraceField.INLINE_3D, 'CROSSLINE_3D', t0);
+    spec = Segy.interpret_segycube(filename, TraceField.INLINE_3D, 193, t0);
+    data = Segy.get_cube( spec );
+
+    assert(all(size(data) == [50, 5, 5]));
+catch
+    %nothing should be caught
+    assert(false);
+end
+
+% fail when file doesn't exist
+
+try
+    Segy.get_header('does-not-exist', 193);
+    assert(false);
+catch
+    assert(true);
+end
+
+try
+    Segy.get_traces('does-not-exist', 189, 193 );
+    assert(false);
+catch
+    assert(true);
+end
+
+try
+    Segy.get_ntraces('does-not-exist');
+    assert(false);
+catch
+    assert(true);
+end
+
+try
+    Segy.interpret_segycube('does-not-exist');
+    assert(false);
+catch
+    assert(true);
+end
+
+%% Segy.readInLine 4
+
+spec = Segy.interpret_segycube(filename, 'INLINE_3D', 'CROSSLINE_3D', t0);
+data = Segy.get_line(spec, 'iline', 4);
+sample_count = length(spec.sample_indexes);
+
+eps = 1e-4;
+% first trace along xline
+% first sample
+assert(abs(data(1, 1) - 4.2)<eps);
+% middle sample
+assert(abs(data(sample_count/2,1)-4.20024)<eps);
+% last sample
+assert(abs(data(sample_count,1)-4.20049)<eps);
+
+% middle trace along xline
+middle = 3;
+% first sample
+assert(abs(data(1, middle) - 4.22) < eps);
+% middle sample
+assert(abs(data(sample_count/2,middle)-4.22024)<eps);
+% last sample
+assert(abs(data(sample_count,middle)-4.22049)<eps);
+
+% middle trace along xline
+last = length(spec.crossline_indexes);
+% first sample
+assert(abs(data(1, last) - 4.24) < eps);
+% middle sample
+assert(abs(data(sample_count/2,last)-4.24024)<eps);
+% last sample
+assert(abs(data(sample_count,last)-4.24049)<eps);
+
+%% Segy.readCrossLine 1433
+
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+data = Segy.readCrossLine(spec, 20);
+data = Segy.readCrossLine(spec, 21);
+data = Segy.readCrossLine(spec, 22);
+data = Segy.readCrossLine(spec, 23);
+data = Segy.readCrossLine(spec, 24);
+data = Segy.readCrossLine(spec, 22);
+sample_count = length(spec.sample_indexes);
+
+eps = 1e-4;
+% first trace along iline
+% first sample
+assert(abs(data(1, 1) - 1.22) < eps);
+% middle sample
+assert(abs(data(sample_count/2,1)-1.22024)<eps);
+% last sample
+assert(abs(data(sample_count,1)-1.22049)<eps);
+
+% middle trace along iline
+middle = 3;
+% first sample
+assert(abs(data(1, middle) - 3.22) < eps);
+% middle sample
+assert(abs(data(sample_count/2,middle)-3.22029)<eps);
+% last sample
+assert(abs(data(sample_count,middle)-3.22049)<eps);
+
+% middle trace along iline
+last = length(spec.inline_indexes);
+% first sample
+assert(abs(data(1, last) - 5.22) < eps);
+% middle sample
+assert(abs(data(sample_count/2,last)-5.22029)<eps);
+% last sample
+assert(abs(data(sample_count,last)-5.22049)<eps);
+
+filename_write = 'test-data/SEGY-3D_write_mex.sgy';
+
+copyfile(filename, filename_write)
+
+spec = SegySpec(filename_write, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+data = Segy.get_line(spec, 'xline', 22);
+
+assert(abs(data(1, 1) - 1.22) < eps);
+
+data(1,1) = 100;
+
+Segy.writeCrossLine(spec, data, 22);
+
+data = Segy.readCrossLine(spec, 22);
+assert(data(1, 1) == 100);
+
+data = Segy.readInLine(spec, 4);
+
+assert(abs(data(1, 1) - 4.2) < eps);
+
+data(1,1) = 200;
+
+Segy.writeInLine(spec, data, 4);
+
+data = Segy.readInLine(spec, 4);
+assert(data(1, 1) == 200);
+
+[~, dt, nt] = Segy.get_traces(filename);
+assert(dt == 4000);
+assert(nt == 1);
+
+[headers, notraces] = Segy.get_header(filename, 'INLINE_3D');
+assert(isequal((1:5), unique(headers)));
+assert(notraces == 25)
+
+assert(Segy.get_ntraces(filename) == 25);
+
+
+% Goal:
+%   Fast writing of segy file
+%
+% Inputs:
+%   filename         Filename of segyfile to write
+%   filename_orig    Filename of segyfile to copy header
+%   data:            Data
+%   nt:              Number of time samples
+%   nxl:             Number of Xlines
+%   nil:             Number of Inlines
+%
+% function provided by Matteo Ravasi
+
+Segy_struct_orig = Segy.interpret_segycube(filename,'INLINE_3D','CROSSLINE_3D');
+data = Segy.get_traces(filename);
+data = data + 1000;
+
+nt = 50;
+nxl = 5;
+nil = 5;
+
+if( ( nt  == numel( Segy_struct_orig.t ) ) &&...
+    ( nxl == numel( Segy_struct_orig.xline ) ) &&...
+    ( nil == numel( Segy_struct_orig.iline ) ) )
+
+    data = reshape( data, [nt, nxl*nil] );
+
+    filename_copy = 'test-data/SEGY-3D_copy.sgy';
+    copyfile( filename, filename_copy );
+
+    Segy.put_traces( filename_copy, data, 1, nxl*nil );
+    spec = Segy.interpret_segycube( filename_copy );
+    data = Segy.get_line(spec, 'iline', 4);
+
+    assert(abs(data(1, 1) - 1004.2) < eps);
+    assert(abs(data(sample_count/2,1) - 1004.20024) < eps);
+    assert(abs(data(sample_count,1) - 1004.20049) < eps);
+
+    middle = 3;
+    assert(abs(data(1, middle) - 1004.22) < eps);
+    assert(abs(data(sample_count/2, middle) - 1004.22024) < eps);
+    assert(abs(data(sample_count, middle) - 1004.22049) < eps);
+
+    last = length(spec.crossline_indexes);
+    assert(abs(data(1, last) - 1004.24) < eps);
+    assert(abs(data(sample_count/2, last) - 1004.24024) < eps);
+    assert(abs(data(sample_count, last) - 1004.24049) < eps);
+else
+    assert(false);
+end
+
+% test put_line
+data = Segy.get_line(spec, 'iline', 4);
+data = data + 100;
+p1 = data(sample_count/2, 1);
+p2 = data(sample_count, 1);
+Segy.put_line(spec, data, 'iline', 4);
+data = Segy.get_line( spec, 'iline', 4);
+assert(all([p1, p2] == [data(sample_count/2, 1), data(sample_count, 1)]));
+
+% read trace headers and file headers
+dummy = Segy.get_segy_header( filename );
+dummy = Segy.get_trace_header( filename, 0 );
+dummy = Segy.get_trace_header( filename, 10 );
+
+Segy.put_headers( filename, 10, 'CDP' );
+Segy.get_header( filename, 'CDP' );
+
+increasing = linspace( 1, notraces, notraces );
+Segy.put_headers( filename_copy, increasing, 'offset' );
+assert( all(increasing == Segy.get_header( filename_copy, 'offset' )) );
diff --git a/tests/test_segyspec.c b/tests/test_segyspec.c
new file mode 100644
index 0000000..9ed8477
--- /dev/null
+++ b/tests/test_segyspec.c
@@ -0,0 +1,79 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "unittest.h"
+
+#include <spec/segyspec.h>
+#include <segyio/segy.h>
+
+
+void testSegyInspection() {
+    const char *path = "test-data/small.sgy";
+    double t0 = 1111.0;
+
+    SegySpec spec;
+
+    segyCreateSpec(&spec, path, INLINE_3D, CROSSLINE_3D, t0);
+
+    assertTrue(spec.sample_format == IBM_FLOAT_4_BYTE, "Expected the float format to be IBM Float");
+
+    assertTrue(strcmp(spec.filename, path) == 0, "The paths did not match");
+
+    assertTrue(spec.offset_count == 1, "Expected offset to be 1");
+
+    assertTrue(spec.trace_sorting_format == INLINE_SORTING, "Expected sorting to be INLINE_SORTING");
+
+
+    assertTrue(spec.sample_count == 50, "Expected sample count to be 50");
+
+    for(int i = 0; i < spec.sample_count; i++) {
+        double t = t0 + i * 4.0;
+        assertTrue(spec.sample_indexes[i] == t, "Sample index not equal to expected value");
+    }
+
+    assertTrue(spec.inline_count == 5, "Expect inline count to be 5");
+    for(int i = 0; i < spec.inline_count; i++) {
+        unsigned int il = spec.inline_indexes[i];
+        assertTrue(il >= 1 && il <= 5, "Expected inline index value to be between [1, 5]");
+    }
+
+    assertTrue(spec.crossline_count == 5, "Expect crossline count to be 5");
+    for(int i = 0; i < spec.crossline_count; i++) {
+        unsigned int xl = spec.crossline_indexes[i];
+        assertTrue(xl >= 20 && xl <= 24, "Expected crossline index value to be between [20, 24]");
+    }
+
+    if (spec.crossline_indexes != NULL)
+        free(spec.crossline_indexes);
+    if (spec.inline_indexes != NULL)
+        free(spec.inline_indexes);
+    if (spec.sample_indexes != NULL)
+        free(spec.sample_indexes);
+
+    free(spec.filename);
+
+}
+
+void testAlloc(){
+    const char *path = "test-data/small.sgy";
+    double t0 = 1111.0;
+    SegySpec spec;
+    segyCreateSpec(&spec, path, INLINE_3D, CROSSLINE_3D, t0);
+
+    if (spec.crossline_indexes != NULL)
+        free(spec.crossline_indexes);
+    if (spec.inline_indexes != NULL)
+        free(spec.inline_indexes);
+    if (spec.sample_indexes != NULL)
+        free(spec.sample_indexes);
+
+    free(spec.filename);
+
+}
+
+int main() {
+    testAlloc();
+    testSegyInspection();
+    exit(0);
+}
+
diff --git a/tests/test_segyspec_mex.m b/tests/test_segyspec_mex.m
new file mode 100644
index 0000000..e3c29d0
--- /dev/null
+++ b/tests/test_segyspec_mex.m
@@ -0,0 +1,88 @@
+% test segyspec
+
+% preconditions 
+filename = 'test-data/small.sgy';
+assert(exist(filename,'file')==2);
+t0 = 1111.0;
+
+%% no such file
+no_such_filename = 'no-such-dir/no-such-file.sgy';
+assert(exist(no_such_filename,'file')~=2);
+try
+    spec = SegySpec(no_such_filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+    %should not reach here
+    assert(false);
+catch
+    %not actually needed...
+    assert(true);
+end
+
+%% Spec is created
+try
+    spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+catch
+    %nothing should be caught
+    assert(false);
+end
+
+%% IBM_FLOAT_4_BYTE
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+assert(spec.sample_format == SegySampleFormat.IBM_FLOAT_4_BYTE);
+
+%% filename is set
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+assert(strcmp(spec.filename,filename));
+
+%% trace_sorting_format
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+assert(spec.trace_sorting_format == TraceSortingFormat.INLINE);
+
+%%offset_count
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+assert(length(spec.offset_count) == 1);
+
+%% samples
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+sample_indexes = spec.sample_indexes;
+assert(length(sample_indexes) == 50);
+
+%% first_trace_pos
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+first_trace_pos = spec.first_trace_pos;
+assert(first_trace_pos == 3600);
+
+%% il_stride
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+il_stride = spec.il_stride;
+assert(il_stride == 1);
+
+%% xl_stride
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+xl_stride = spec.xl_stride;
+assert(xl_stride == 5);
+
+%% xl_stride
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+trace_bsize = spec.trace_bsize;
+assert(trace_bsize == 50*4);
+
+
+for i = 1:length(sample_indexes)
+    t = t0 + (i-1) * 4;
+    assert(sample_indexes(i) == t);
+end
+
+%% xline
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+assert(length(spec.crossline_indexes)==5)
+for xl = spec.crossline_indexes'
+    assert(xl >= 20 && xl <= 24);
+end
+
+%% iline
+spec = SegySpec(filename, TraceField.INLINE_3D, TraceField.CROSSLINE_3D, t0);
+assert(length(spec.inline_indexes)==5)
+
+for il = spec.inline_indexes'
+    assert(il >= 1 && il <= 5);
+end
diff --git a/tests/test_utils.c b/tests/test_utils.c
new file mode 100644
index 0000000..a88164d
--- /dev/null
+++ b/tests/test_utils.c
@@ -0,0 +1,128 @@
+#include <stdlib.h>
+#include <string.h>
+#include <segyio/segy.h>
+#include "unittest.h"
+
+void ebcdic2ascii( const char*, char* );
+void ascii2ebcdic( const char*, char* );
+
+void testEbcdicConversion() {
+    char* expected = (char*) "Hello there!";
+    char str[] = "\xc8\x85\x93\x93\x96\x40\xa3\x88\x85\x99\x85\x4f";
+
+    char result[strlen(expected)];
+
+    ebcdic2ascii(str, result);
+    assertTrue(strcmp(result, expected) == 0, "Converted string did not match the expected result!");
+
+    ascii2ebcdic(result, result);
+    assertTrue(strcmp(result, str) == 0, "Converted string did not match the expected result!");
+}
+
+void testEbcdicTable() {
+    char ascii[256];
+    for (unsigned char i = 0; i < 255; i++) {
+        ascii[i] = (char) (i + 1);
+    }
+    ascii[255] = 0;
+
+    char ebcdic[256];
+    ascii2ebcdic((const char*) ascii, ebcdic);
+
+    char result_ascii[256];
+    ebcdic2ascii(ebcdic, result_ascii);
+
+    assertTrue(strcmp(ascii, result_ascii) == 0, "Conversion from EBCDIC to ASCII to EBCDIC failed!");
+    for(unsigned char i = 0; i < 255; i++) {
+    }
+}
+
+void testConversionAllocation() {
+    char* expected = (char*) "Hello there!";
+    char str[] = "\xc8\x85\x93\x93\x96\x40\xa3\x88\x85\x99\x85\x4f";
+
+    char result[strlen(str) + 1];
+    ebcdic2ascii(str, result);
+    assertTrue(strcmp(result, expected) == 0, "Converted string did not match the expected result!");
+
+    ascii2ebcdic(expected, result);
+    assertTrue(strcmp(result, str) == 0, "Converted string did not match the expected result!");
+}
+
+#define MAX 1000000 /* number of iterations */
+#define IBM_EPS 4.7683738e-7 /* worst case error */
+
+void ibm2ieee(void* to, const void* from, int len);
+void ieee2ibm(void* to, const void* from, int len);
+
+static void check(float f1, double * epsm) {
+    int exp;
+    float f2;
+    double eps;
+    unsigned ibm1, ibm2;
+
+    frexp(f1, &exp);
+    ieee2ibm(&ibm1, &f1, 1);
+    ibm2ieee(&f2, &ibm1, 1);
+    ieee2ibm(&ibm2, &f2, 1);
+
+    assertTrue(memcmp(&ibm1, &ibm2, sizeof ibm1) == 0, "The content of two memory areas were not identical!");
+    //printf("Error: %08x <=> %08x\n", *(unsigned*) &ibm1, *(unsigned*) &ibm2);
+
+    eps = ldexp(fabs(f1 - f2), -exp);
+    if (eps > *epsm) {
+        *epsm = eps;
+    }
+
+    assertTrue(eps < IBM_EPS, "Difference over conversion larger than allowed epsilon!");
+    //printf("Error: %.8g != %.8g\n", f1, f2);
+}
+
+void testIBMFloat() {
+    int i;
+    float f1;
+
+    double epsm = 0.0;
+    for (i = 0; i < MAX; i++) {
+        f1 = rand();
+        check(f1, &epsm);
+        check(-f1, &epsm);
+    }
+    printf("Max eps: %g\n", epsm);
+}
+
+int to_int16( const char* );
+int to_int32( const char* );
+
+int16_t from_int16( int16_t );
+int32_t from_int32( int32_t );
+
+void test_integer_round_trip() {
+    /* this test probably only works as expected on intel/x86 */
+    /* this is what data looks like when read from segy */
+    char buf16[ 2 ] = { 000, 001 }; /* 1 */
+    /* unsigned to avoid overflow warning on 0257 */
+    unsigned char buf32[ 4 ] = { 0, 0, 011, 0257 };
+
+    int i1 = to_int16( buf16 );
+    int i2479 = to_int32( (char*)buf32 );
+    assertTrue( i1 == 1, "Expected SEGY two's complement 2-byte 1 => 1" );
+    assertTrue( i2479 == 2479, "Expected SEGY two's complement 4-byte 2479 => 2479" );
+
+    int16_t round_int16 = from_int16( 1 );
+    int32_t round_int32 = from_int32( 2479 );
+
+    assertTrue( memcmp( &round_int16, buf16, sizeof( round_int16 ) ) == 0,
+                "int16 did not survive round trip" );
+    assertTrue( memcmp( &round_int32, buf32, sizeof( round_int32 ) ) == 0,
+                "int32 did not survive round trip" );
+}
+
+int main() {
+    testEbcdicConversion();
+    testEbcdicTable();
+    testConversionAllocation();
+    testIBMFloat();
+    test_integer_round_trip();
+    exit(0);
+}
diff --git a/tests/unittest.h b/tests/unittest.h
new file mode 100644
index 0000000..c0e3194
--- /dev/null
+++ b/tests/unittest.h
@@ -0,0 +1,43 @@
+#ifndef SEGYIO_UNITTEST_H
+#define SEGYIO_UNITTEST_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <math.h>
+
+void testAssertionFailed(const char *message, const char *file, int line) {
+    fprintf(stderr, "Assertion failed in file: %s on line: %d\n", file, line);
+    if (strlen(message) > 0) {
+        fprintf(stderr, message);
+    }
+    exit(1);
+}
+
+#define assertTrue(value, message) _testAssertTrue((value), (message), __FILE__, __LINE__)
+
+void _testAssertTrue(bool value, const char *message, const char *file, int line) {
+    if (!value) {
+        if (strlen(message) == 0) {
+            message = "The expression did not evaluate to true!";
+        }
+        testAssertionFailed(message, file, line);
+    }
+}
+
+#define assertClose(expected, actual, eps) _testAssertClose((expected), (actual), (eps), __FILE__, __LINE__)
+
+void _testAssertClose(float expected, float actual, float eps, const char *file, int line) {
+    float diff = fabsf(expected-actual);
+    if (!(diff <= eps)) {
+        char message[1000];
+        sprintf(message, "Expected: %f, Actual: %f, diff: %f, eps: %f\n", expected, actual, diff, eps);
+        testAssertionFailed(message, file, line);
+    }
+}
+
+
+
+#endif //SEGYIO_UNITTEST_H

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