diff --git a/.github/actions/benchmark/action.yaml b/.github/actions/benchmark/action.yaml new file mode 100644 index 00000000..8a32fc80 --- /dev/null +++ b/.github/actions/benchmark/action.yaml @@ -0,0 +1,88 @@ +name: Benchmark +description: benchmark execution + +inputs: + ref: + description: ref to checkout + compare: + description: results comparison string + default: --benchmark-compare=0001 --benchmark-compare-fail=min:25% --benchmark-group-by=fullname --color=yes + +runs: + using: "composite" + steps: + - name: Print available disk space + shell: bash + run: df -h + + - name: Checkout commit which performance should be measured + shell: bash + run: | + git restore . + git fetch origin ${{ inputs.ref }} --depth 1 + git checkout ${{ inputs.ref }} + + - name: Build wheel + shell: bash + env: + CIBW_BUILD_VERBOSITY: 1 + run: | + python -m cibuildwheel --only cp312-manylinux_x86_64 --output-dir ./wheelhouse python/ + + - name: Install local wheel + shell: bash + run: | + pip install ./wheelhouse/*.whl + rm -f ./wheelhouse/*.whl + + - name: Checkout current commit which contains all tests to run + shell: bash + run: git checkout ${{ github.sha }} + + # assure that local segyio doesn't take priority (one installed to pip is used) + - name: Remove local segyio + shell: bash + working-directory: python + run: | + python -c "import segyio; import inspect; print('before: segyio loaded from ' + inspect.getfile(segyio))" + rm -rf $(pwd)/segyio + python -c "import segyio; import inspect; print('after: segyio loaded from ' + inspect.getfile(segyio))" + + - name: Make segy file + shell: bash + working-directory: python + run: | + if [ ! -f file.sgy ]; then + time python -m examples.make-file file.sgy 1600 1 1000 1 1200 + else + echo "file.sgy already exists" + fi + + - name: Make segy small file + shell: bash + working-directory: python + run: | + if [ ! -f file-small.sgy ]; then + time python -m examples.make-file file-small.sgy 500 1 350 1 350 + else + echo "file-small.sgy already exists" + fi + + - name: Print filesize + shell: bash + working-directory: python + run: | + ls -lh file.sgy + ls -lh file-small.sgy + + - name: Run benchmark tests + shell: bash + working-directory: python + run: | + pytest test/benchmarks.py -rP --benchmark-sort=name --benchmark-autosave ${{ inputs.compare }} + + - name: Remove build artifacts + shell: bash + run: | + git clean -df --exclude=.benchmarks + pip uninstall -y segyio diff --git a/.github/images/bigendian.Dockerfile b/.github/images/bigendian.Dockerfile new file mode 100644 index 00000000..5fad2d17 --- /dev/null +++ b/.github/images/bigendian.Dockerfile @@ -0,0 +1,18 @@ +ARG S390X_BASE_IMAGE=s390x_base +FROM s390x/debian:stable-slim AS s390x_base +RUN apt-get update +RUN apt-get install -y git cmake g++ python3 python3-pip python3-venv python3-numpy + +FROM $S390X_BASE_IMAGE AS tester +WORKDIR / +COPY . /segyio +WORKDIR /segyio + +RUN python3 -m venv pyvenv --system-site-packages +RUN /segyio/pyvenv/bin/python -m pip install --upgrade pip +RUN /segyio/pyvenv/bin/python -m pip install -r python/requirements-dev.txt + +WORKDIR /segyio/build +RUN cmake -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug -DPython_ROOT_DIR="/segyio/pyvenv/" .. +RUN make -j4 +RUN ctest --verbose diff --git a/.github/images/large32-bit.Dockerfile b/.github/images/large32-bit.Dockerfile new file mode 100644 index 00000000..68066330 --- /dev/null +++ b/.github/images/large32-bit.Dockerfile @@ -0,0 +1,17 @@ +FROM i386/python:3-slim +RUN apt-get update +RUN apt-get install -y git cmake g++ libopenblas-dev + +WORKDIR / +COPY . /segyio +WORKDIR /segyio + +RUN pip install --upgrade pip +RUN pip install -r python/requirements-dev.txt + +WORKDIR /segyio/build +RUN cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_BIN=OFF -DBUILD_TESTING=OFF -DBUILD_PYTHON=ON .. +RUN make -j4 + +WORKDIR /segyio/python +RUN pytest test/large.py -rPV diff --git a/.github/utils.py b/.github/utils.py deleted file mode 100644 index c1ce5710..00000000 --- a/.github/utils.py +++ /dev/null @@ -1,37 +0,0 @@ -import argparse -import shutil -import sys - -def cptree(argv): - parser = argparse.ArgumentParser(prog = 'shutil.copytree') - parser.add_argument('-r', '--recursive', action = 'store_true') - parser.add_argument('--src', type = str) - parser.add_argument('--dst', type = str) - args = parser.parse_args(argv) - - if args.recursive: - shutil.copytree(args.src, args.dst) - else: - shutil.copyfile(args.src, args.dst) - -def rmtree(argv): - parser = argparse.ArgumentParser(prog = 'shutil.rmtree') - parser.add_argument('--paths', nargs = '*', type = str) - args = parser.parse_args(argv) - - for path in args.paths: - shutil.rmtree(path) - -if __name__ == '__main__': - """Command line utils - - The CI pipeline is executed in bash on linux/mac and in PowerShell on - Windows. This mini-script handles a couple of commands that differ in the - two shells. - """ - parser = argparse.ArgumentParser(prog = 'shutil') - parser.add_argument('cmd', choices = ['copy', 'remove']) - args = parser.parse_args(sys.argv[1:2]) - - if args.cmd == 'copy': cptree(sys.argv[2:]) - if args.cmd == 'remove': rmtree(sys.argv[2:]) diff --git a/.github/workflows/analyzers.yaml b/.github/workflows/analyzers.yaml new file mode 100644 index 00000000..715f00ce --- /dev/null +++ b/.github/workflows/analyzers.yaml @@ -0,0 +1,99 @@ +name: Run analyzers +permissions: {} + +on: + push: + branches: [segyio-1.x] + pull_request: + branches: [segyio-1.x] + workflow_dispatch: + +jobs: + cppcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get -y install cppcheck + + - name: Configure + run: | + cmake -S . -B build \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DBUILD_PYTHON=OFF \ + -DBUILD_TESTING=OFF + + - name: Run cppcheck + run: | + cppcheck \ + --enable=style,portability,performance,warning \ + --library=posix \ + --library=cppcheck/segyio.cfg \ + --suppressions-list=cppcheck/suppressions.txt \ + --inline-suppr \ + --project=build/compile_commands.json \ + --error-exitcode=1 + + scan_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get -y install clang clang-tools libfindbin-libs-perl + + - name: Configure segyio + run: | + scan-build --status-bugs \ + cmake \ + -S . \ + -B build \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_PYTHON=OFF \ + -DBUILD_TESTING=OFF + + - name: Run scan-build + run: | + scan-build --status-bugs \ + cmake \ + --build build + + valgrind: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get -y install valgrind + + - name: Build segyio + run: | + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_FLAGS_DEBUG="-O0" \ + -DCMAKE_CXX_FLAGS_DEBUG="-O0" \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_PYTHON=OFF + cmake --build build + + - name: Run valgrind on C tests, file + working-directory: build/lib + run: | + valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./c.segy "[c.segy]" + + - name: Run valgrind on C tests, mmap + working-directory: build/lib + run: | + valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./c.segy "[c.segy]" "--test-mmap" + + - name: Run valgrind on C++ tests + working-directory: build/lib + run: | + valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./c.segy "[c++]" diff --git a/.github/workflows/benchmarks.yaml b/.github/workflows/benchmarks.yaml new file mode 100644 index 00000000..9d773fbb --- /dev/null +++ b/.github/workflows/benchmarks.yaml @@ -0,0 +1,54 @@ +name: Benchmarks +permissions: {} + +on: + push: + branches: [segyio-1.x] + pull_request: + branches: [segyio-1.x] + workflow_dispatch: + inputs: + commit_ref: + description: "optional: compare to specific ref" + required: false + +jobs: + benchmarks: + name: Benchmark scripts + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install cibuildwheel + # old ref is incompatible with cmake 4.0.0, which is used in cbuildwheel > 2.23.2 + # once default ref is updated, restriction can be removed + run: python3 -m pip install cibuildwheel==2.23.2 + + - name: Install build/test dependencies + working-directory: python + run: | + python3 -m pip install -r requirements-dev.txt + + - name: Set default benchmark reference + run: echo "BENCHMARK_REF=142e45a2b941a1b603723c38882b193db1c1d968" >> $GITHUB_ENV + + - name: Set benchmark reference if defined by workflow_dispatch + if: github.event.inputs.commit_ref != '' + run: echo "BENCHMARK_REF=${{ github.event.inputs.commit_ref }}" >> $GITHUB_ENV + + - name: Benchmark old commit + uses: "./.github/actions/benchmark" + with: + ref: ${{ env.BENCHMARK_REF }} + compare: "" + + # Head of repository is incompatible with cibuildwheel < 3.0.0 due to + # using the "test-sources" feature in pyproject.toml + - name: Update cibuildwheel for new commit + run: python3 -m pip install --upgrade cibuildwheel + + - name: Benchmark current commit and compare + uses: "./.github/actions/benchmark" + with: + ref: ${{ github.sha }} diff --git a/.github/workflows/bigendian.yaml b/.github/workflows/bigendian.yaml new file mode 100644 index 00000000..25b17c75 --- /dev/null +++ b/.github/workflows/bigendian.yaml @@ -0,0 +1,82 @@ +name: Test bigendian +permissions: {} + +on: + push: + branches: [segyio-1.x] + pull_request: + branches: [segyio-1.x] + workflow_dispatch: + +jobs: + bigendian: + name: Run tests on bigendian s390x system + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + with: + # driver must be docker, likely related to https://github.com/moby/buildkit/issues/2343 + driver: docker + + - name: Set caching parameters + run: | + dockerfile_sha=$(sha1sum .github/images/bigendian.Dockerfile| head -c 40) + echo "DOCKERFILE_SHA=$dockerfile_sha" >> $GITHUB_ENV + echo "S390X_CACHE_DIR=~/s390x_image" >> $GITHUB_ENV + echo "S390X_CACHE_FILE=s390x.tar" >> $GITHUB_ENV + echo "S390X_IMAGE_TAG=s390x:$dockerfile_sha" >> $GITHUB_ENV + + - name: Make s390x cache directory + run: mkdir -p ${{ env.S390X_CACHE_DIR }} + + - name: Check for s390x cache + uses: actions/cache@v4 + id: s390x-cache + with: + path: ${{ env.S390X_CACHE_DIR }} + key: s390x-cache-${{ env.DOCKERFILE_SHA }} + + - if: steps.s390x-cache.outputs.cache-hit != 'true' + name: Build s390x image + uses: docker/build-push-action@v6 + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: .github/images/bigendian.Dockerfile + load: true + platforms: linux/s390x + target: s390x_base + tags: ${{ env.S390X_IMAGE_TAG }} + + - if: steps.s390x-cache.outputs.cache-hit != 'true' + name: Save built s390x image + run: | + echo "s390x image from dockerfile with sha ${{ env.DOCKERFILE_SHA }} not found in cache" + echo "made s390x image with tag: ${{ env.S390X_IMAGE_TAG }}" + docker save -o ${{ env.S390X_CACHE_DIR }}/${{ env.S390X_CACHE_FILE }} ${{ env.S390X_IMAGE_TAG }} + + - if: steps.s390x-cache.outputs.cache-hit == 'true' + name: Load s390x image + run: | + ls ${{ env.S390X_CACHE_DIR }} + docker load -i ${{ env.S390X_CACHE_DIR }}/${{ env.S390X_CACHE_FILE }} + + - name: Build + uses: docker/build-push-action@v6 + with: + builder: ${{ steps.buildx.outputs.name }} + build-args: | + S390X_BASE_IMAGE=${{ env.S390X_IMAGE_TAG }} + context: . + file: .github/images/bigendian.Dockerfile + platforms: linux/s390x + target: tester diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 70b108b3..323920d4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,36 +1,75 @@ name: Build and Test +permissions: {} on: push: - branches: [master] + branches: [segyio-1.x] pull_request: - branches: [master] + branches: [segyio-1.x] workflow_dispatch: jobs: test: runs-on: ${{ matrix.os }} - name: Build and test on ${{ matrix.os }} ${{ matrix.compiler }} + name: Build and test ${{ matrix.build_type }} on ${{ matrix.os }} ${{ matrix.compiler }} ${{ matrix.cmake_generator }} BUILD_PYTHON=${{ matrix.build_python }} strategy: + fail-fast: false matrix: include: - - os: windows-2019 - cmake_generator: "-G \"Visual Studio 16 2019\" -A x64" - - os: windows-2019 - cmake_generator: "-G \"Visual Studio 16 2019\" -A Win32" - - os: macos-latest + - os: windows-2022 + cmake_generator: "-G \"Visual Studio 17 2022\" -A x64" + build_type: "Release" + compiler_flags: "-D_CRT_SECURE_NO_WARNINGS /EHsc" + build_python: "OFF" + - os: windows-2022 + cmake_generator: "-G \"Visual Studio 17 2022\" -A x64" + build_type: "Debug" + compiler_flags: "-D_CRT_SECURE_NO_WARNINGS /EHsc" + build_python: "OFF" + - os: windows-2022 + cmake_generator: "-G \"Visual Studio 17 2022\" -A Win32" + build_type: "Release" + compiler_flags: "-D_CRT_SECURE_NO_WARNINGS /EHsc" + build_python: "OFF" + - os: ubuntu-latest + privledges: "sudo" + build_type: "Release" + build_python: "OFF" + - os: ubuntu-latest privledges: "sudo" - - os: ubuntu-20.04 + build_type: "Debug" + compiler_flags: "-Wextra -Wall -pedantic" + build_python: "OFF" + - os: ubuntu-latest privledges: "sudo" - - os: ubuntu-20.04 + build_type: "Debug" + compiler_flags: "-Wextra -Wall -pedantic" + build_python: "ON" + - os: ubuntu-latest privledges: "sudo" compiler: "clang" - analyzers: "cppcheck" - scan: "scan-build --status-bugs" mkdoc: "-DBUILD_DOC=ON -DSPHINX_ARGS=-WT" + build_type: "Release" + build_python: "OFF" + - os: ubuntu-latest + privledges: "sudo" + compiler: "clang" + build_type: "Debug" + compiler_flags: "-Wextra -Wall -pedantic" + build_python: "OFF" + - os: macos-15-intel + privledges: "sudo" + build_type: "Release" + build_python: "OFF" - os: macos-latest privledges: "sudo" - arch: arm64 + build_type: "Release" + build_python: "OFF" + - os: macos-latest + privledges: "sudo" + build_type: "Debug" + compiler_flags: "-Wextra -Wall -pedantic" + build_python: "ON" steps: - uses: actions/checkout@v4 @@ -43,28 +82,30 @@ jobs: echo "CC=/usr/bin/clang" >> $GITHUB_ENV echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV - - name: Run cppcheck - shell: bash - if: ${{ matrix.analyzer == 'cppcheck' }} + # Required to be able to install Python packages on MacOS + - uses: actions/setup-python@v6 + if: ${{ matrix.build_python == 'ON' }} + with: + python-version: '3.13' + + - name: Install build/test for Python dependencies + working-directory: python + if: ${{ matrix.build_python == 'ON' }} run: | - cppcheck - --enable=style,portability,performance,warning - --library=posix - --library=cppcheck/segyio.cfg - --suppressions-list=cppcheck/suppressions.txt - --inline-suppr - --project=compile_commands.json - --error-exitcode=1 + python3 -m pip install -r requirements-dev.txt - name: Configure shell: bash run: | - ${{ matrix.scan }} cmake -S . -B build \ + cmake -S . -B build \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DBUILD_PYTHON=OFF \ + -DBUILD_PYTHON=${{ matrix.build_python }} \ -DBUILD_SHARED_LIBS=ON \ - -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_COMPILE_WARNING_AS_ERROR=ON \ -DCMAKE_INSTALL_NAME_DIR=/usr/local/lib \ + -DCMAKE_C_FLAGS="${{ matrix.compiler_flags }}" \ + -DCMAKE_CXX_FLAGS="${{ matrix.compiler_flags }}" \ ${{ matrix.mkdoc }} \ ${{ matrix.cmake_generator }} \ @@ -73,11 +114,55 @@ jobs: run: | ${{ matrix.privledges }} cmake \ --build build \ - --config Release \ + --config ${{ matrix.build_type }} \ --target install \ - name: Test shell: bash run: | cd build - ctest -C Release --output-on-failure + ctest -C ${{ matrix.build_type }} --output-on-failure -V + + + matlab: + runs-on: ubuntu-latest + name: Build and test matlab code + + steps: + - uses: actions/checkout@v4 + + - name: Set up MATLAB + uses: matlab-actions/setup-matlab@v2 + + - name: Configure + shell: bash + run: | + cmake -S . -B build \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DBUILD_PYTHON=OFF \ + -DBUILD_MEX=ON \ + -DBUILD_BIN=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_NAME_DIR=/usr/local/lib \ + + - name: Build and Install + shell: bash + run: | + sudo cmake \ + --build build \ + --config Release \ + --target install \ + + - name: Set LD_LIBRARY_PATH + run: echo "LD_LIBRARY_PATH=/usr/local/lib" >> $GITHUB_ENV + + - name: Run 'segy.m' test script + uses: matlab-actions/run-command@v2 + with: + command: addpath('build/mex'), try, run('build/mex/test/segy.m'), exit(0), catch ME, disp(getReport(ME, 'extended')), exit(-1), end; + + - name: Run 'segyspec.m' test script + uses: matlab-actions/run-command@v2 + with: + command: addpath('build/mex'), try, run('build/mex/test/segyspec.m'), exit(0), catch ME, disp(getReport(ME, 'extended')), exit(-1), end; diff --git a/.github/workflows/large.yaml b/.github/workflows/large.yaml new file mode 100644 index 00000000..8c1301e9 --- /dev/null +++ b/.github/workflows/large.yaml @@ -0,0 +1,139 @@ +name: Run large tests +permissions: {} + +on: + push: + branches: [segyio-1.x] + pull_request: + branches: [segyio-1.x] + workflow_dispatch: + +jobs: + large: + name: Run large tests on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # only 64-bit windows + - os: windows-2022 + cmake_generator: '-G "Visual Studio 17 2022"' + - os: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build/test dependencies + working-directory: python + run: | + python -m pip install -r requirements-dev.txt + + - name: Configure + shell: bash + run: | + cmake -S . -B build \ + -DBUILD_PYTHON=ON \ + -DBUILD_TESTING=OFF \ + -DBUILD_BIN=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + ${{ matrix.cmake_generator }} \ + + - name: Build + shell: bash + run: | + cmake \ + --build build \ + --config Release \ + + - name: Test + shell: bash + run: | + cd python + pytest test/large.py -rPV + + large-32-bit-linux: + name: Run large tests on 32-bit linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Build + uses: docker/build-push-action@v6 + with: + builder: ${{ steps.buildx.outputs.name }} + context: . + file: .github/images/large32-bit.Dockerfile + platforms: linux/386 + + # The only true way to test 32-bit library is to run it on 32-bit machine, but + # there are no 32-bit runner in Github Actions. + # + # Running compiled 32-bit library on 64-bit machine is a substitute. It is not + # clear how well it represents true 32-bit system, but it is our only option. + # + # Building python code directly, without cbuildwheel, with 32-bit MSVC compiler + # fails. Seems like skbuild forces native platform internally, even if we supply + # 32-bit one directly. Build works in cbuildwheel as they seem to hack the + # environment to make it look like 32-bit system. + large_win32: + name: Run large tests on windows-2022, 32-bit wheel + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + architecture: "x86" + + - name: Install cibuildwheel + run: python3 -m pip install cibuildwheel + + - name: Install build/test dependencies + working-directory: python + run: | + python -m pip install -r requirements-dev.txt + + - name: Build wheel + shell: bash + env: + CIBW_BUILD_VERBOSITY: 1 + CIBW_ENVIRONMENT_WINDOWS: > + CMAKE_GENERATOR="Visual Studio 17 2022" + CMAKE_GENERATOR_PLATFORM="Win32" + run: | + python -m cibuildwheel --only cp312-win32 --output-dir ./wheelhouse python/ + + - name: Install local wheel + shell: bash + run: | + pip install ./wheelhouse/*.whl + rm -f ./wheelhouse/*.whl + + # assure that local segyio doesn't take priority (one installed to pip is used) + - name: Remove local segyio + shell: bash + working-directory: python + run: | + python -c "import segyio; import inspect; print('before: segyio loaded from ' + inspect.getfile(segyio))" + rm -rf $(pwd)/segyio + python -c "import segyio; import inspect; print('after: segyio loaded from ' + inspect.getfile(segyio))" + + - name: Test + shell: bash + run: | + cd python + pytest test/large.py -rPV diff --git a/.github/workflows/wheels.yaml b/.github/workflows/wheels.yaml index 5cd6fbc3..616f79d4 100644 --- a/.github/workflows/wheels.yaml +++ b/.github/workflows/wheels.yaml @@ -1,12 +1,13 @@ name: Wheels +permissions: {} on: push: - branches: [master] + branches: [segyio-1.x] tags: - '*' pull_request: - branches: [master] + branches: [segyio-1.x] workflow_dispatch: jobs: @@ -17,23 +18,23 @@ jobs: fail-fast: false matrix: include: - - os: windows-2019 - cmake_generator: "Visual Studio 16 2019" + - os: windows-2022 + cmake_generator: "Visual Studio 17 2022" cmake_generator_platform: "x64" arch: AMD64 - - os: windows-2019 - cmake_generator: "Visual Studio 16 2019" + - os: windows-2022 + cmake_generator: "Visual Studio 17 2022" cmake_generator_platform: "Win32" arch: x86 - - os: ubuntu-20.04 + - os: ubuntu-latest arch: x86_64 - os: ubuntu-24.04-arm arch: aarch64 - - os: ubuntu-20.04 + - os: ubuntu-latest arch: i686 # macos-latest runs on arm64 architecture and cibuildwheel cross # compiling for x86_64 fails - - os: macos-13 + - os: macos-15-intel arch: x86_64 - os: macos-latest arch: arm64 @@ -54,7 +55,7 @@ jobs: CIBW_ENVIRONMENT_WINDOWS: > CMAKE_GENERATOR="${{ matrix.cmake_generator }}" CMAKE_GENERATOR_PLATFORM="${{ matrix.cmake_generator_platform }}" - CIBW_SKIP: pp* *-musllinux_* cp36-* cp37-* cp38-* + CIBW_SKIP: cp38-* cp39-* *-musllinux_* CIBW_ARCHS: ${{ matrix.arch }} run: | python -m cibuildwheel --output-dir wheelhouse python/ @@ -69,6 +70,13 @@ jobs: name: Publish wheels to PyPI runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + + environment: + name: pypi + + permissions: + id-token: write + steps: - uses: actions/checkout@v4 @@ -79,9 +87,6 @@ jobs: path: ./wheelhouse/ - name: Publish wheels to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} packages-dir: ./wheelhouse/ diff --git a/.pyup.yml b/.pyup.yml index 604f69f7..6143d800 100644 --- a/.pyup.yml +++ b/.pyup.yml @@ -1,4 +1,4 @@ -# autogenerated pyup.io config file +# autogenerated pyup.io config file # see https://pyup.io/docs/configuration/ for all available options update: insecure diff --git a/CMakeLists.txt b/CMakeLists.txt index 41e9d639..28955867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.18) project(segyio LANGUAGES C CXX) include(CheckFunctionExists) @@ -61,7 +61,6 @@ endif () option(BUILD_SHARED_LIBS "Build language bindings shared" OFF) option(BUILD_BIN "Build applications" ON) option(BUILD_PYTHON "Build Python library" ON) -option(REQUIRE_PYTHON "Fail cmake if python cannot be built" OFF) option(BUILD_MEX "Build Matlab mex files" OFF) option(BUILD_DOC "Build documentation" OFF) option(EXPERIMENTAL "Enable experimental features" OFF) @@ -125,7 +124,9 @@ add_subdirectory(external/catch2) add_subdirectory(lib) # language bindings add_subdirectory(mex) -add_subdirectory(python) +if (BUILD_PYTHON) + add_subdirectory(python) +endif() add_subdirectory(applications) add_subdirectory(man) diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index f7abb48f..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include License.md -include README.md -recursive-include src *.h diff --git a/README.md b/README.md index 0a67c15f..3570ee12 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ # segyio # -[![Travis](https://img.shields.io/travis/equinor/segyio/master.svg?label=travis)](https://travis-ci.org/equinor/segyio) -[![Appveyor](https://ci.appveyor.com/api/projects/status/2i5cr8ui2t9qbxk9?svg=true)](https://ci.appveyor.com/project/statoil-travis/segyio) -[![PyPI Updates](https://pyup.io/repos/github/equinor/segyio/shield.svg)](https://pyup.io/repos/github/equinor/segyio/) -[![Python 3](https://pyup.io/repos/github/equinor/segyio/python-3-shield.svg)](https://pyup.io/repos/github/equinor/segyio/) +[![PyPI - Version](https://img.shields.io/pypi/v/segyio)](https://pypi.org/project/segyio/) +[![Read the Docs](https://img.shields.io/readthedocs/segyio)](https://segyio.readthedocs.io/) + +> [!NOTE] +> 🚧 *segyio 2.0* is under construction in the `main` branch. 🚧 +> +> New major release is intended to have better support for SEG-Y 2.1 revision. +> +> ⚠️ Users should be prepared for possible breaking changes. ## Documentation ## @@ -90,11 +95,9 @@ A copy of segyio is available both as pre-built binaries and source code: To build segyio you need: * A C99 compatible C compiler (tested mostly on gcc and clang) * A C++ compiler for the Python extension, and C++11 for the tests - * [CMake](https://cmake.org/) version 2.8.12 or greater - * [Python](https://www.python.org/) 3.9 or greater + * [CMake](https://cmake.org/) version 3.18 or greater + * [Python](https://www.python.org/) 3.10 or greater * [numpy](http://www.numpy.org/) version 1.10 or greater - * [setuptools](https://pypi.python.org/pypi/setuptools) version 28 or greater - * [setuptools-scm](https://pypi.python.org/pypi/setuptools_scm) * [pytest](https://pypi.org/project/pytest) To build the documentation, you also need @@ -119,7 +122,7 @@ LD_LIBRARY_PATH and PATH). If you have multiple Python installations, or want to use some alternative interpreter, you can help cmake find the right one by passing -`-DPYTHON_EXECUTABLE=/opt/python/binary` along with install prefix and build +`-DPython_ROOT_DIR=/opt/python/binary` along with install prefix and build type. To build the matlab bindings, invoke CMake with the option `-DBUILD_MEX=ON`. In @@ -196,8 +199,7 @@ The segy file object has several public attributes describing this structure: * `f.ext_headers` The number of extended textual headers -If the file is opened *unstructured*, all the line properties will will be -`None`. +If the file is opened *unstructured*, all the line properties will be `None`. ### Modes ### @@ -355,9 +357,10 @@ discriminate between the revisions, but instead tries to use information available in the file. For an *actual* standard's reference, please see the publications by SEG: -- [SEG-Y 0 (1975)](https://seg.org/Portals/0/SEG/News%20and%20Resources/Technical%20Standards/seg_y_rev0.pdf) -- [SEG-Y 1 (2002)](https://seg.org/Portals/0/SEG/News%20and%20Resources/Technical%20Standards/seg_y_rev1.pdf) -- [SEG-Y 2 (2017)](https://seg.org/Portals/0/SEG/News%20and%20Resources/Technical%20Standards/seg_y_rev2_0-mar2017.pdf) +- [SEG-Y 0 (1975)](https://seg.org/wp-content/uploads/2025/11/seg_y_rev0.pdf) +- [SEG-Y 1 (2002)](https://seg.org/wp-content/uploads/2025/11/seg_y_rev1.pdf) +- [SEG-Y 2 (2017)](https://seg.org/wp-content/uploads/2025/11/seg_y_rev2_0_mar2017.pdf) +- [SEG-Y 2.1 (2023)](https://seg.org/wp-content/uploads/2025/11/seg_y_rev2_1-oct2023.pdf) ## Contributing ## @@ -373,8 +376,8 @@ demos in this ## Reproducing the test data ## Small SEG-Y formatted files are included in the repository for test purposes. -The data is non-sensical and made to be predictable, and it is reproducible by -using segyio. The tests file are located in the test-data directory. To +The data is nonsensical and made to be predictable, and it is reproducible by +using segyio. The test files are located in the test-data directory. To reproduce the data file, build segyio and run the test program `make-file.py`, `make-ps-file.py`, and `make-rotated-copies.py` as such: @@ -398,8 +401,8 @@ segyread tape=small.sgy ns=50 remap=tracr,cdp byte=189l,193l conv=1 format=1 \ suswapbytes < small.su > small-lsb.su ``` -If you have have small data files with a free license, feel free to submit it -to the project! +If you have small data files with a free license, feel free to submit it to the +project! ## Examples ## @@ -509,7 +512,7 @@ with segyio.open(output_file, "r+") as src: src.iline[i] = 2 * src.iline[i] ``` -[Make segy file from sctrach](python/examples/make-file.py) +[Make segy file from scratch](python/examples/make-file.py) ### MATLAB ### diff --git a/applications/segyinfo.c b/applications/segyinfo.c index 86faa91f..9c4b2197 100644 --- a/applications/segyinfo.c +++ b/applications/segyinfo.c @@ -23,7 +23,7 @@ static void printSegyTraceInfo( const char* buf ) { #define maximum(x,y) ((x) > (y) ? (x) : (y)) int main(int argc, char* argv[]) { - + if( argc < 2 ) { puts("Missing argument, expected run signature:"); printf(" %s [mmap]\n", argv[0]); diff --git a/applications/segyinspect.c b/applications/segyinspect.c index 952aff3b..27e17fcf 100644 --- a/applications/segyinspect.c +++ b/applications/segyinspect.c @@ -9,21 +9,21 @@ static const char* getSampleFormatName( int format ) { switch( format ) { case SEGY_IBM_FLOAT_4_BYTE: - return "IBM Float"; + return "IBM Float"; case SEGY_SIGNED_INTEGER_4_BYTE: - return "Int 32"; + return "Int 32"; case SEGY_SIGNED_SHORT_2_BYTE: - return "Int 16"; + return "Int 16"; case SEGY_FIXED_POINT_WITH_GAIN_4_BYTE: - return "Fixed Point with gain (Obsolete)"; + return "Fixed Point with gain (Obsolete)"; case SEGY_IEEE_FLOAT_4_BYTE: - return "IEEE Float"; + return "IEEE Float"; case SEGY_NOT_IN_USE_1: - return "Not in Use 1"; + return "Not in Use 1"; case SEGY_NOT_IN_USE_2: - return "Not in Use 2"; + return "Not in Use 2"; case SEGY_SIGNED_CHAR_1_BYTE: - return "Int 8"; + return "Int 8"; default: return "Unknown"; } diff --git a/applications/segyio-catb.c b/applications/segyio-catb.c index d5514e70..9b0ca7e2 100644 --- a/applications/segyio-catb.c +++ b/applications/segyio-catb.c @@ -9,7 +9,7 @@ #include "apputils.h" #include -static int printhelp(){ +static int printhelp(void){ puts( "Usage: segyio-catb [OPTION]... [FILE]...\n" "Concatenate the binary header from FILE(s) to seismic unix " "output.\n" @@ -23,7 +23,7 @@ static int printhelp(){ return 0; } -static int get_binary_value( char* binheader, int bfield ){ +static int get_binary_value( const char* binheader, int bfield ){ int32_t f; segy_get_bfield( binheader, bfield, &f ); @@ -54,7 +54,7 @@ static struct options parse_options( int argc, char** argv ){ opts.description = 0; opts.version = 0, opts.help = 0; opts.errmsg = NULL; - + static struct option long_options[] = { {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, 'h'}, @@ -64,7 +64,7 @@ static struct options parse_options( int argc, char** argv ){ }; opterr = 1; - + while( true ){ int option_index = 0; @@ -72,7 +72,7 @@ static struct options parse_options( int argc, char** argv ){ long_options, &option_index); if ( c == -1 ) break; - + switch( c ){ case 0: break; case 'h': opts.help = 1; return opts; @@ -156,7 +156,7 @@ int main( int argc, char** argv ){ "Number of 3200-byte, Extended Textual File Headers" }; - static int bfield_value[ 30 ] = { + static const int bfield_value[ 30 ] = { SEGY_BIN_JOB_ID, SEGY_BIN_LINE_NUMBER, SEGY_BIN_REEL_NUMBER, @@ -188,7 +188,7 @@ int main( int argc, char** argv ){ SEGY_BIN_TRACE_FLAG, SEGY_BIN_EXT_HEADERS }; - + if( argc == 1 ){ int err = errmsg(2, "Missing argument\n"); printhelp(); @@ -196,7 +196,7 @@ int main( int argc, char** argv ){ } struct options opts = parse_options( argc, argv ); - + if( opts.help ) return printhelp(); if( opts.version ) return printversion( "segyio-catb" ); @@ -206,9 +206,9 @@ int main( int argc, char** argv ){ if( !fp ) return errmsg(opterr, "No such file or directory"); char binheader[ SEGY_BINARY_HEADER_SIZE ]; - int err = segy_binheader( fp, binheader ); + int err = segy_binheader( fp, binheader ); - if( err ) return errmsg(opterr, "Unable to read binary header"); + if( err ) return errmsg(opterr, "Unable to read binary header"); for( int c = 0; c < 30; ++c ){ int field = get_binary_value( binheader, bfield_value[ c ] ); @@ -229,4 +229,3 @@ int main( int argc, char** argv ){ } return 0; } - diff --git a/applications/segyio-cath.c b/applications/segyio-cath.c index 48e5cce1..22f47688 100644 --- a/applications/segyio-cath.c +++ b/applications/segyio-cath.c @@ -9,7 +9,7 @@ #include "apputils.c" #include -static int help() { +static int help(void) { puts( "Usage: segyio-cath [OPTION]... [FILE]...\n" "Concatenate the textual header(s) from FILE(s) to standard output.\n" "\n" diff --git a/applications/segyio-catr.c b/applications/segyio-catr.c index 0a4564e0..a1ddcb82 100644 --- a/applications/segyio-catr.c +++ b/applications/segyio-catr.c @@ -395,7 +395,7 @@ static const char* desc[91] = { "Unassigned 2" }; -static int help() { +static int help(void) { puts( "Usage: segyio-catr [OPTION]... FILE\n" "Print specific trace headers from FILE\n" @@ -550,6 +550,11 @@ static struct options parse_options( int argc, char** argv ){ ret = sscanf(optarg," %d %d %d", &r->start, &r->stop, &r->step ); + if ( ret == EOF ) { + opts.errmsg = "input error while parsing range parameters"; + return opts; + } + if( ret && ( r->start < 0 || r->stop < 0 || r->step < 0 ) ) { opts.errmsg = "range parameters must be positive"; return opts; @@ -725,4 +730,3 @@ int main( int argc, char** argv ) { return 0; } - diff --git a/applications/segyio-crop.c b/applications/segyio-crop.c index 97638850..c8fa93d9 100644 --- a/applications/segyio-crop.c +++ b/applications/segyio-crop.c @@ -10,7 +10,7 @@ #include "apputils.c" #include -static int help() { +static int help(void) { puts( "Usage: segyio-crop [OPTION]... SRC DST\n" "Copy a sub cube from SRC to DST\n" "\n" diff --git a/bandit.yml b/bandit.yml index ec2e3a91..75d550c3 100644 --- a/bandit.yml +++ b/bandit.yml @@ -1 +1 @@ -skips: ['B101'] \ No newline at end of file +skips: ['B101'] diff --git a/external/catch2/CMakeLists.txt b/external/catch2/CMakeLists.txt index e5088023..dd2baa42 100644 --- a/external/catch2/CMakeLists.txt +++ b/external/catch2/CMakeLists.txt @@ -1,12 +1,5 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.18) project(catch2 CXX) -# Dummy source file added because INTERFACE type -# library is not available in CMake 2.8.12 -# it's STATIC, because MSVC would otherwise not generate a .lib file, making -# "linking" (for header path) fail later -# -# TODO: when cmake minimum version is bumped to 3.x series, replace with -# an INTERFACE library -add_library(catch2 STATIC dummy.cpp) -target_include_directories(catch2 SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +add_library(catch2 INTERFACE) +target_include_directories(catch2 SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/external/catch2/dummy.cpp b/external/catch2/dummy.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2bd45413..d6aba1d1 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.18) project(libsegyio C CXX) set(SEGYIO_LIB_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR} CACHE PATH "") @@ -132,9 +132,26 @@ target_compile_options(c.segy BEFORE target_compile_definitions(c.segy PRIVATE ${mmap} + $<${HOST_BIG_ENDIAN}:HOST_BIG_ENDIAN> ) -add_test(NAME c.segy COMMAND c.segy) -add_test(NAME c.segy.mmap COMMAND c.segy --test-mmap) -add_test(NAME c.segy.lsb COMMAND c.segy --test-lsb) -add_test(NAME c.segy.mmap.lsb COMMAND c.segy --test-mmap --test-lsb) +add_test(NAME c.segy COMMAND c.segy [c.segy]) +add_test(NAME c.segy.mmap COMMAND c.segy [c.segy] --test-mmap) +add_test(NAME c.segy.lsb COMMAND c.segy [c.segy] --test-lsb) +add_test(NAME c.segy.mmap.lsb COMMAND c.segy [c.segy] --test-mmap --test-lsb) add_test(NAME cpp.segy COMMAND c.segy [c++]) + + +# These targets check if code that includes the public C and C++ header compile +# correctly. This is done to detect issues like missing includes directives in +# these headers of segyio. Missing includes may not be picked up by the tests +# above because the test framework does include some C++ dependencies like +# `cstdint` and `algorithm` that may be used in the segyio headers. +add_executable(c-include.segy test/test-include.c) +target_link_libraries(c-include.segy segyio) +target_include_directories(c-include.segy PRIVATE src) +add_test(NAME c-include.segy COMMAND c-include.segy) + +add_executable(cpp-include.segy test/test-include.cpp) +target_link_libraries(cpp-include.segy segyio::segyio) +target_include_directories(cpp-include.segy PRIVATE experimental) +add_test(NAME cpp-include.segy COMMAND cpp-include.segy) diff --git a/lib/experimental/segyio/segyio.hpp b/lib/experimental/segyio/segyio.hpp index 706f07ff..87a9cbf3 100644 --- a/lib/experimental/segyio/segyio.hpp +++ b/lib/experimental/segyio/segyio.hpp @@ -1,12 +1,15 @@ #ifndef SEGYIO_HPP #define SEGYIO_HPP +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -1012,8 +1015,14 @@ simple_handle< T >::simple_handle( const segyio::path& path, } auto p = std::string(path); - std::unique_ptr< std::FILE, decltype( &std::fclose ) > - file( std::fopen( p.c_str(), m.c_str() ), std::fclose ); + + struct file_deleter { + void operator()(std::FILE* fp) const { + std::fclose(fp); + } + }; + std::unique_ptr< std::FILE, file_deleter > + file( std::fopen( p.c_str(), m.c_str() )); if( file ) { /* mode isn't garbage, and path apparently is ok too */ diff --git a/lib/src/segy.c b/lib/src/segy.c index 274ff477..4538917c 100644 --- a/lib/src/segy.c +++ b/lib/src/segy.c @@ -168,9 +168,9 @@ void ascii2ebcdic( const char* ascii, char* ebcdic ) { #define IEMINIB 0x21200000 static inline void ibm_native( void* buf ) { - static int it[8] = { 0x21800000, 0x21400000, 0x21000000, 0x21000000, - 0x20c00000, 0x20c00000, 0x20c00000, 0x20c00000 }; - static int mt[8] = { 8, 4, 2, 2, 1, 1, 1, 1 }; + static const int it[8] = { 0x21800000, 0x21400000, 0x21000000, 0x21000000, + 0x20c00000, 0x20c00000, 0x20c00000, 0x20c00000 }; + static const int mt[8] = { 8, 4, 2, 2, 1, 1, 1, 1 }; unsigned int manthi, iexp, inabs; int ix; uint32_t u; @@ -189,8 +189,8 @@ static inline void ibm_native( void* buf ) { } static inline void native_ibm( void* buf ) { - static int it[4] = { 0x21200000, 0x21400000, 0x21800000, 0x22100000 }; - static int mt[4] = { 2, 4, 8, 1 }; + static const int it[4] = { 0x21200000, 0x21400000, 0x21800000, 0x22100000 }; + static const int mt[4] = { 2, 4, 8, 1 }; unsigned int manthi, iexp, ix; uint32_t u; @@ -223,7 +223,7 @@ void ieee2ibm( void* to, const void* from ) { } /* Lookup table for field sizes. All values not explicitly set are 0 */ -static int field_size[] = { +static int field_size[SEGY_TRACE_HEADER_SIZE] = { [SEGY_TR_CDP_X ] = 4, [SEGY_TR_CDP_Y ] = 4, [SEGY_TR_CROSSLINE ] = 4, @@ -389,7 +389,7 @@ static int file_size( FILE* fp, long long* size ) { // this means we're on windows where fstat is unreliable for filesizes >2G // because long is only 4 bytes struct _stati64 st; - const int err = _fstati64( fileno( fp ), &st ); + const int err = _fstati64( _fileno( fp ), &st ); #else struct stat st; const int err = fstat( fileno( fp ), &st ); @@ -899,7 +899,7 @@ int segy_binheader( segy_file* fp, char* buf ) { #ifdef HAVE_MMAP if( fp->addr ) { - char* src = (char*)fp->addr + SEGY_TEXT_HEADER_SIZE; + const char* src = (char*)fp->addr + SEGY_TEXT_HEADER_SIZE; const int len = SEGY_BINARY_HEADER_SIZE; const int err = memread( buf, fp, src, len ); if( err ) return err; @@ -1279,7 +1279,7 @@ int segy_traces( segy_file* fp, assert( size / trace_bsize <= (long long)INT_MAX ); - *traces = size / trace_bsize; + *traces = (int) (size / trace_bsize); return SEGY_OK; } @@ -1308,8 +1308,8 @@ int segy_sample_interval( segy_file* fp, float fallback, float* dt ) { segy_get_bfield( bin_header, SEGY_BIN_INTERVAL, &bindt ); segy_get_field( trace_header, SEGY_TR_SAMPLE_INTER, &trdt ); - float binary_header_dt = bindt; - float trace_header_dt = trdt; + float binary_header_dt = (float) bindt; + float trace_header_dt = (float) trdt; /* * 3 cases: @@ -1776,7 +1776,7 @@ int segy_readtrace( segy_file* fp, static int bswap64vec( void* vec, long long len ) { char* begin = (char*) vec; - char* end = (char*) begin + len * sizeof(int64_t); + const char* end = (char*) begin + len * sizeof(int64_t); for (char* xs = begin; xs != end; xs += sizeof(int64_t)) { uint64_t v; @@ -1790,7 +1790,7 @@ static int bswap64vec( void* vec, long long len ) { static int bswap32vec( void* vec, long long len ) { char* begin = (char*) vec; - char* end = (char*) begin + len * sizeof(int32_t); + const char* end = (char*) begin + len * sizeof(int32_t); for( char* xs = begin; xs != end; xs += sizeof(int32_t) ) { uint32_t v; @@ -1804,7 +1804,7 @@ static int bswap32vec( void* vec, long long len ) { static int bswap24vec( void* vec, long long len ) { char* begin = (char*) vec; - char* end = (char*) begin + len * 3; + const char* end = (char*) begin + len * 3; for (char* xs = begin; xs != end; xs += 3) { uint8_t bits[3]; @@ -1821,7 +1821,7 @@ static int bswap24vec( void* vec, long long len ) { static int bswap16vec( void* vec, long long len ) { char* begin = (char*) vec; - char* end = (char*) begin + len * sizeof(int16_t); + const char* end = (char*) begin + len * sizeof(int16_t); for( char* xs = begin; xs != end; xs += sizeof(int16_t) ) { uint16_t v; @@ -1856,7 +1856,7 @@ int segy_readsubtr( segy_file* fp, err = memread( buf, fp, fp->cur, elemsize * elems ); if( err != SEGY_OK ) return err; } else { - const int readc = fread( buf, elemsize, elems, fp->fp ); + const int readc = (int) fread( buf, elemsize, elems, fp->fp ); if( readc != elems ) return SEGY_FREAD_ERROR; } @@ -1907,7 +1907,7 @@ int segy_readsubtr( segy_file* fp, */ void* tracebuf = rangebuf ? rangebuf : malloc( elems * elemsize ); - const int readc = fread( tracebuf, elemsize, elems, fp->fp ); + const int readc = (int) fread( tracebuf, elemsize, elems, fp->fp ); if( readc != elems ) { if( !rangebuf ) free( tracebuf ); return SEGY_FREAD_ERROR; @@ -2010,7 +2010,7 @@ int segy_writesubtr( segy_file* fp, err = memwrite( fp, fp->cur, buf, elemsize * elems ); if( err ) return err; } else { - const int writec = fwrite( buf, elemsize, elems, fp->fp ); + const int writec = (int) fwrite( buf, elemsize, elems, fp->fp ); if( writec != elems ) return SEGY_FWRITE_ERROR; } @@ -2036,7 +2036,7 @@ int segy_writesubtr( segy_file* fp, * only handle fstream path - the mmap is handled comfortably by the * stride-aware code path */ - const int writec = fwrite( tracebuf, elemsize, elems, fp->fp ); + const int writec = (int) fwrite( tracebuf, elemsize, elems, fp->fp ); if( !rangebuf ) free( tracebuf ); if( writec != elems ) return SEGY_FWRITE_ERROR; return SEGY_OK; @@ -2071,7 +2071,7 @@ int segy_writesubtr( segy_file* fp, void* tracebuf = rangebuf ? rangebuf : malloc( elems * elemsize ); // like in readsubtr, read a larger chunk and then step through that - const int readc = fread( tracebuf, elemsize, elems, fp->fp ); + const int readc = (int) fread( tracebuf, elemsize, elems, fp->fp ); if( readc != elems ) { free( tracebuf ); return SEGY_FREAD_ERROR; } /* rewind, because fread advances the file pointer */ err = fseek( fp->fp, -(elems * elemsize), SEEK_CUR ); @@ -2093,7 +2093,7 @@ int segy_writesubtr( segy_file* fp, bswap16vec_strided( cur, src, step, slicelen ); } - const int writec = fwrite( tracebuf, elemsize, elems, fp->fp ); + const int writec = (int) fwrite( tracebuf, elemsize, elems, fp->fp ); if( !rangebuf ) free( tracebuf ); if( writec != elems ) return SEGY_FWRITE_ERROR; @@ -2345,8 +2345,7 @@ int segy_write_textheader( segy_file* fp, int pos, const char* buf ) { if( pos < 0 ) return SEGY_INVALID_ARGS; - err = encode( mbuf, buf, a2e, SEGY_TEXT_HEADER_SIZE ); - if( err != 0 ) return err; + encode( mbuf, buf, a2e, SEGY_TEXT_HEADER_SIZE ); const long offset = pos == 0 ? 0 @@ -2399,9 +2398,9 @@ static int scaled_cdp( segy_file* fp, err = segy_get_field( trheader, SEGY_TR_SOURCE_GROUP_SCALAR, &scalar ); if( err != 0 ) return err; - float scale = scalar; + float scale = (float) scalar; if( scalar == 0 ) scale = 1.0; - if( scalar < 0 ) scale = -1.0 / scale; + if( scalar < 0 ) scale = -1.0f / scale; *cdpx = x * scale; *cdpy = y * scale; @@ -2441,9 +2440,9 @@ int segy_rotation_cw( segy_file* fp, float x = nw.x - sw.x; float y = nw.y - sw.y; - float radians = x || y ? atan2( x, y ) : 0; + double radians = x || y ? atan2( x, y ) : 0; if( radians < 0 ) radians += 2 * acos(-1); - *rotation = radians; + *rotation = (float) radians; return SEGY_OK; } diff --git a/lib/test/segy.cpp b/lib/test/segy.cpp index 88a4bb1e..00a857cf 100644 --- a/lib/test/segy.cpp +++ b/lib/test/segy.cpp @@ -7,9 +7,21 @@ #include #include +// disable conversion from 'const _Elem' to '_Objty' MSC warnings. +// warnings reason is unknown, should be caused by Catch2 though, thus ignored +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#endif + #include #include "matchers.hpp" +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + #include #include @@ -251,6 +263,9 @@ int arbitrary_int() { */ return -1; } +float arbitrary_float() { + return -1.0f; +} } @@ -262,7 +277,7 @@ TEST_CASE_METHOD( smallbin, segy_get_bfield( bin, SEGY_BIN_INTERVAL, &hdt ); REQUIRE( hdt == 4000 ); - float dt = arbitrary_int(); + float dt = arbitrary_float(); const Err err = segy_sample_interval( fp, 100.0, &dt ); CHECK( err == Err::ok() ); @@ -278,7 +293,7 @@ TEST_CASE( "use fallback interval when both trace and bin is negative", const float fallback = 100.0; - float dt = arbitrary_int(); + float dt = arbitrary_float(); const Err err = segy_sample_interval( fp, fallback, &dt ); CHECK( err == Err::ok() ); @@ -295,7 +310,7 @@ TEST_CASE( "use trace interval when bin is negative", const float fallback = 100.0; const float expected = 4000.0; - float dt = arbitrary_int(); + float dt = arbitrary_float(); const Err err = segy_sample_interval( fp, fallback, &dt ); CHECK( err == Err::ok() ); @@ -312,7 +327,7 @@ TEST_CASE( "use bin interval when trace is negative", const float fallback = 100.0; const float expected = 2000.0; - float dt = arbitrary_int(); + float dt = arbitrary_float(); const Err err = segy_sample_interval( fp, fallback, &dt ); CHECK( err == Err::ok() ); @@ -767,7 +782,7 @@ TEST_CASE_METHOD( smallcube, stride, offsets, inlines.data(), - inlines.size(), + (int) inlines.size(), &line_trace0 ); CHECK( success( err ) ); CHECK( line_trace0 == 15 ); @@ -783,7 +798,7 @@ TEST_CASE_METHOD( smallcube, stride, offsets, inlines.data(), - inlines.size(), + (int) inlines.size(), &line_trace0 ); CHECK( err == SEGY_MISSING_LINE_INDEX ); } @@ -817,7 +832,7 @@ TEST_CASE_METHOD( smallcube, stride, offsets, crosslines.data(), - crosslines.size(), + (int) crosslines.size(), &line_trace0 ); CHECK( success( err ) ); CHECK( line_trace0 == 2 ); @@ -834,7 +849,7 @@ TEST_CASE_METHOD( smallcube, stride, offsets, inlines.data(), - inlines.size(), + (int) inlines.size(), &line_trace0 ); CHECK( err == SEGY_MISSING_LINE_INDEX ); } @@ -974,7 +989,7 @@ TEST_CASE_METHOD( smallcube, std::vector< float > line( expected.size() ); Err err = segy_read_line( fp, line_trace0, - crosslines.size(), + (int) crosslines.size(), stride, offsets, line.data(), @@ -1011,7 +1026,7 @@ TEST_CASE_METHOD( smallcube, std::vector< float > line( expected.size() ); Err err = segy_read_line( fp, line_trace0, - inlines.size(), + (int) inlines.size(), stride, offsets, line.data(), @@ -1508,78 +1523,75 @@ TEST_CASE("open file with >32k traces", "[c.segy]") { CHECK(samples == 60000); } -SCENARIO( "reading a large file", "[c.segy]" ) { - GIVEN( "a large file" ) { - const char* file = "4G-file.sgy"; - - unique_segy ufp( segy_open( file, "w+b" ) ); - auto fp = ufp.get(); - - const int trace = 5000000; - const int trace_bsize = 1000; - const long long tracesize = trace_bsize + SEGY_TRACE_HEADER_SIZE; - const long trace0 = 0; - - const Err err = segy_seek( fp, trace, trace0, trace_bsize ); - CHECK( err == Err::ok() ); - WHEN( "reading past 4GB (pos >32bit)" ) { - THEN( "there is no overflow" ) { - const long long pos = segy_ftell( fp ); - CHECK( pos > std::numeric_limits< int >::max() ); - CHECK( pos != -1 ); - CHECK( pos == trace * tracesize ); - } - } - } -} +#ifdef HOST_BIG_ENDIAN + #define HOST_LSB 0 + #define HOST_MSB 1 +#else + #define HOST_LSB 1 + #define HOST_MSB 0 +#endif /* * There is no native 3-byte integral type in C++, so hack a minimal one * together. We don't need arithmetic, only conversion to int32 and from int16 * (which is what the source file is in). * - * It's quite incomplete in the sense that it's really unaware of signed - * integers, but what's important is its size and its individual bytes, and how - * it is created from int16. The test file was created by just adding a single, - * zero byte at the most-significant byte position, and otherwise memcpy'd. + * Type is incomplete, but important parts of implementation are type's size, + * its individual bytes, and how it is created from int16. The test files were + * created by just adding a single, 0x00 byte or 0xFF byte at the + * most-significant byte position, and otherwise memcpy'd. */ -struct int24 { - char bytes[3]; +struct int24_base { + unsigned char bytes[3]; - int24() = default; + int24_base() = default; // cppcheck-suppress noExplicitConstructor - int24(const int16_t& x) { + int24_base(const int16_t& x) { + /* Sign is allowed to be negative even if type represents unsigned int, + * which is done to keep behavior consistent with that of other formats + */ + char sign = x < 0 ? -1 : 0; +#if HOST_LSB this->bytes[0] = ((const char*)&x)[0]; this->bytes[1] = ((const char*)&x)[1]; - this->bytes[2] = 0; + this->bytes[2] = sign; +#else + this->bytes[0] = sign; + this->bytes[1] = ((const char*)&x)[0]; + this->bytes[2] = ((const char*)&x)[1]; +#endif } operator std::int32_t () const noexcept (true) { - return (this->bytes[0] << 0) - | (this->bytes[1] << 8) - | (this->bytes[2] << 16) - ; - } - - bool operator == (int24 rhs) const noexcept (true) { - return std::int32_t(*this) == std::int32_t(rhs); - } - - bool operator != (int24 rhs) const noexcept (true) { - return !(*this == rhs); +#if HOST_LSB + return (static_cast(this->bytes[0]) << 0) + | (static_cast(this->bytes[1]) << 8) + | (static_cast(this->bytes[2]) << 16) + | (static_cast(this->bytes[2]) << 24); +#else + return (static_cast(this->bytes[0]) << 24) + | (static_cast(this->bytes[0]) << 16) + | (static_cast(this->bytes[1]) << 8) + | (static_cast(this->bytes[2]) << 0); + +#endif } }; static_assert( - sizeof(int24) == 3, + sizeof(int24_base) == 3, "int24 type is padded, but is expected to be 3 bytes" ); static_assert( - std::is_standard_layout< int24 >::value, + std::is_standard_layout< int24_base >::value, "int24 must be standard layout" ); +using int24 = int24_base; +using uint24 = int24_base; + + /* * open a copy of f3, but pre-converted to a different format, to check that * other formats are read correctly @@ -1665,6 +1677,10 @@ TEST_CASE("can open 1-byte signed char", "[c.segy][format]") { } TEST_CASE("can open 3-byte signed char", "[c.segy][format][uniq]") { + // int24 implementation sanity check + REQUIRE(static_cast(int24(int16_t(500))) == 500); + REQUIRE(static_cast(int24(int16_t(-500))) == -500); + f3_in_format< int24 >(SEGY_SIGNED_CHAR_3_BYTE); } @@ -1685,7 +1701,10 @@ TEST_CASE("can open 8-byte unsigned integer", "[c.segy][format]") { } TEST_CASE("can open 3-byte unsigned integer", "[c.segy][format]") { - f3_in_format< int24 >(SEGY_UNSIGNED_INTEGER_3_BYTE); + // uint24 implementation sanity check + REQUIRE(static_cast(uint24(int16_t(500))) == 500); + + f3_in_format< uint24 >(SEGY_UNSIGNED_INTEGER_3_BYTE); } TEST_CASE("can open 1-byte unsigned char", "[c.segy][format]") { diff --git a/lib/test/segyio-cpp.cpp b/lib/test/segyio-cpp.cpp index 18cd40c4..aec11e09 100644 --- a/lib/test/segyio-cpp.cpp +++ b/lib/test/segyio-cpp.cpp @@ -1,8 +1,20 @@ #include #include "matchers.hpp" +// MSC conversion warnings in segyio.hpp are ignored as +// it is unclear how to fix them in segyio.hpp template code +// pragmas could be removed if proper fix is found +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#endif + #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + using namespace segyio; using namespace segyio::literals; @@ -223,7 +235,7 @@ TEST_CASE_METHOD( Writable, "[c++]" ) { std::vector< float > in( 50 ); - for( std::size_t i = 0; i < in.size(); ++i ) in[i] = i; + for( std::size_t i = 0; i < in.size(); ++i ) in[i] = (float) i; f.put( 0, in.begin() ); std::vector< float > out; diff --git a/lib/test/test-include.c b/lib/test/test-include.c new file mode 100644 index 00000000..db4fbc0b --- /dev/null +++ b/lib/test/test-include.c @@ -0,0 +1,5 @@ +#include + +int main(void) { + return 0; +} diff --git a/lib/test/test-include.cpp b/lib/test/test-include.cpp new file mode 100644 index 00000000..aa8c4465 --- /dev/null +++ b/lib/test/test-include.cpp @@ -0,0 +1,10 @@ +#include + +int main() { + // Open a SEG-Y file to avoid error about unused internal functions. + segyio::basic_volume< segyio::readonly > tmp( + segyio::path{ "test-data/small.sgy" }, + segyio::config{} + ); + return 0; +} diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt index e92bd49d..9f96d7cc 100644 --- a/man/CMakeLists.txt +++ b/man/CMakeLists.txt @@ -7,6 +7,6 @@ endif() install(FILES segyio-cath.1 segyio-catb.1 segyio-catr.1 - segyio-crop.1 + segyio-crop.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) diff --git a/man/segyio-catb.1 b/man/segyio-catb.1 index fea70b78..4feaa032 100644 --- a/man/segyio-catb.1 +++ b/man/segyio-catb.1 @@ -30,7 +30,7 @@ display this help and exit Copyright © Statoil ASA. License LGPLv3+: GNU LGPL version 3 or later . .PP -This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. +This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH SEE ALSO segyio-cath(1), segyio-catb(1) diff --git a/mex/CMakeLists.txt b/mex/CMakeLists.txt index 2a1a6654..4fb5cbc0 100644 --- a/mex/CMakeLists.txt +++ b/mex/CMakeLists.txt @@ -19,7 +19,7 @@ configure_file(SegySampleFormat.m SegySampleFormat.m) configure_file(TraceSortingFormat.m TraceSortingFormat.m) configure_file(TraceField.m TraceField.m) -get_property(dirs TARGET segyio-shared PROPERTY INCLUDE_DIRECTORIES) +get_property(dirs TARGET segyio PROPERTY INCLUDE_DIRECTORIES) include_directories(${dirs}) mexo(segyutil) diff --git a/mex/TraceSortingFormat.m b/mex/TraceSortingFormat.m index c6ceea7c..f2192967 100644 --- a/mex/TraceSortingFormat.m +++ b/mex/TraceSortingFormat.m @@ -6,4 +6,3 @@ iline (2) end end - diff --git a/mex/test/segy.m b/mex/test/segy.m index 2e2f5b54..6963d893 100644 --- a/mex/test/segy.m +++ b/mex/test/segy.m @@ -1,3 +1,4 @@ +disp('segy.m test suite: start'); % test segyline % preconditions @@ -279,3 +280,5 @@ assert(abs(wr_line1(2,2,1) - 101.01001) < eps); assert(abs(wr_line1(3,2,1) - 101.01002) < eps); assert(abs(wr_line1(2,2,2) - 101.02001) < eps); + +disp('segy.m test suite: over'); diff --git a/mex/test/segyspec.m b/mex/test/segyspec.m index bb8669be..536a1fc7 100644 --- a/mex/test/segyspec.m +++ b/mex/test/segyspec.m @@ -1,6 +1,7 @@ +disp('segyspec.m test suite: start'); % test segyspec -% preconditions +% preconditions filename = 'test-data/small.sgy'; assert(exist(filename,'file')==2); t0 = 1111.0; @@ -86,3 +87,5 @@ for il = spec.inline_indexes' assert(il >= 1 && il <= 5); end + +disp('segyspec.m test suite: over'); diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 6c481b96..c73eb478 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,113 +1,78 @@ -if (SKBUILD) - # invoked as a part of scikit-build, so this is just a proxy for the python - # extension cmake. this works around the fundamental limitation in cmake - # that it looks only for directories with a CMakeLists.txt in it, not for a - # named file - include(setup-CMakeLists.txt) - return () -endif () +cmake_minimum_required(VERSION 3.18) +project(segyio-python LANGUAGES CXX) -cmake_minimum_required(VERSION 3.5) -project(segyio-python) +set(CMAKE_CXX_STANDARD 11) -if (REQUIRE_PYTHON) - set(BUILD_PYTHON ON) +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +# If we execute CMake via a scikit-build-core, i.e., `python -m build`, `pip +# install` etc., we expect that the segyio library is available on the system. +if (SKBUILD) + find_package(segyio REQUIRED) endif() -if (NOT BUILD_PYTHON) - return() -endif() +python_add_library(_segyio MODULE WITH_SOABI segyio/segyio.cpp) +target_link_libraries(_segyio PRIVATE segyio::segyio) -find_package(PythonInterp REQUIRED) +if (MSVC) + target_compile_options(_segyio + BEFORE + PRIVATE + /EHsc + ) +endif () -if (NOT PYTHON_EXECUTABLE AND REQUIRE_PYTHON) - message(SEND_ERROR "Could not find python executable") - return() +if (SKBUILD) + set(SEGYIO_PYTHON_INSTALL_DIR segyio) +else() + # CMake defines Python_SITELIB that points to the system path for Python + # libraries, but we cannot use it here. In case of a non-empty installation + # prefix, CMake would merge the installation path into something like + # ${CMAKE_INSTALL_PREFIX}/${Python_SITELIB} which would lead to the wrong + # full path. For example, it could look like this: + # + # /usr/local/usr/lib/python3.12/site-packages + # PREFIX | SITELIB + # + # This is not a path one would expect the Python library to be in. + set(SEGYIO_PYTHON_INSTALL_DIR lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages/segyio) endif() -if (NOT PYTHON_EXECUTABLE) - message(WARNING "Could not find python - skipping python bindings. " - "Select specific python distribution with " - "-DPYTHON_EXECUTABLE=bin/python") - return() +install(TARGETS _segyio LIBRARY DESTINATION ${SEGYIO_PYTHON_INSTALL_DIR}) + +# Emulate in-place build behavior of invoking `python setup.py build_ext -i`. +# This places the binding library into the `python/segyio/` directory. Doing so +# allows the usage of `ctest` to test the core library, but also the Python +# package. +if (MSVC) + # On Windows, setting the target properties does not work. Whatever is + # built on Windows does not fullfil CMake's definition of "library", + # "archive" or other outputs that for which we could set the output + # directory as target property. Therefore, we copy the library into the + # correct location. + add_custom_command(TARGET _segyio POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_SOURCE_DIR}/segyio + ) +else() + set_target_properties(_segyio PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/segyio + ) endif() -if (PYTHON_INSTALL_LAYOUT) - set(setup-install-layout --install-layout ${PYTHON_INSTALL_LAYOUT}) +# If CMake is run via a scikit-build-core, we have set up everything needed and +# we can return from this file. The installation and collection of relevant +# files is handled by scikit-build-core. Other options, like building the +# documentation, are not available via scikit-build-core. +if (SKBUILD) + return() endif() -set(setup.py ${CMAKE_CURRENT_SOURCE_DIR}/setup.py) -if (CMAKE_BUILD_TYPE) - # use the cmake_build_type of the source project, unless it has been - # specifically overriden - set(SEGYIO_PYTHON_BUILD_TYPE - --build-type=${CMAKE_BUILD_TYPE} - CACHE STRING "override CMAKE_BUILD_TYPE in python extension" - ) -endif () - -add_custom_target( - segyio-python ALL - COMMENT "Building python library with setup.py" - SOURCES ${setup.py} - DEPENDS ${setup.py} - VERBATIM - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - - # if DESTDIR is used, either for build and install, and make (or similar) - # is the generator, the scikit-build's internal install command will - # respect it and install the build-dir shared object there. The cmake - # driven install already respects DESTDIR by translating into --root, so - # simply remove it from then environment when doing python commands - # - # This is probably something that should be addressed upstream - COMMAND ${CMAKE_COMMAND} -E env --unset=DESTDIR - ${PYTHON_EXECUTABLE} ${setup.py} - # build the extension inplace (really, once its built, copy it to the - # source tree) so that post-build, the directory can be used to run - # tests against - build_ext --inplace - build # setup.py build args - --cmake-executable ${CMAKE_COMMAND} - --generator ${CMAKE_GENERATOR} - ${SEGYIO_PYTHON_BUILD_TYPE} - -- # cmake to the extension - -Dsegyio_DIR=${SEGYIO_LIB_BINARY_DIR} - # "install" to the python/dlisio dir with rpath, so there's no need - # to fiddle with environment in ctest to load the core library from - # the build tree - -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON - -DCMAKE_INSTALL_RPATH=$ - -DCMAKE_INSTALL_NAME_DIR=$ -) - -add_dependencies(segyio-python segyio) - -install(CODE " - if (DEFINED ENV{DESTDIR}) - get_filename_component(abs-destdir \"\$ENV{DESTDIR}\" ABSOLUTE) - set(root_destdir --root \${abs-destdir}) - endif() - - if (CMAKE_INSTALL_PREFIX) - set(prefix --prefix \"${CMAKE_INSTALL_PREFIX}\") - endif () - - execute_process( - COMMAND ${CMAKE_COMMAND} -E env --unset=DESTDIR - ${PYTHON_EXECUTABLE} ${setup.py} - install - \${root_destdir} - --single-version-externally-managed - --record record.txt - --cmake-executable \"${CMAKE_COMMAND}\" - --generator \"${CMAKE_GENERATOR}\" - \${prefix} - ${SEGYIO_PYTHON_BUILD_TYPE} - -- - -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=OFF - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - )" +# Explicitly install Python files to avoid copying files that are not required, +# e.g., segyio.cpp or folder like __pycache__ that exist if the tests were run +# via ctest. +install( + DIRECTORY segyio/ + DESTINATION ${SEGYIO_PYTHON_INSTALL_DIR} + FILES_MATCHING PATTERN "*.py" ) option(BUILD_PYDOC "Build python documentation" OFF) @@ -121,7 +86,7 @@ if(BUILD_PYDOC) # run sphinx as the same python version that was just built # otherwise, the it will search for the wrong segyio extension and # fail - COMMAND ${PYTHON_EXECUTABLE} -m sphinx + COMMAND ${Python_EXECUTABLE} -m sphinx # use the -d argument to avoid putting cache dir in docs/, because # that directory will be install'd -d ${CMAKE_CURRENT_BINARY_DIR}/.doctrees @@ -149,7 +114,7 @@ endif() add_test( NAME python.unit - COMMAND ${PYTHON_EXECUTABLE} -m pytest test/ + COMMAND ${Python_EXECUTABLE} -m pytest test/ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -157,49 +122,49 @@ configure_file(../test-data/small.sgy write.sgy COPYONLY) add_test( NAME python.example.about - COMMAND ${PYTHON_EXECUTABLE} -m examples.about + COMMAND ${Python_EXECUTABLE} -m examples.about ../test-data/small.sgy INLINE_3D CROSSLINE_3D WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_test( NAME python.example.write - COMMAND ${PYTHON_EXECUTABLE} -m examples.write + COMMAND ${Python_EXECUTABLE} -m examples.write ${CMAKE_CURRENT_BINARY_DIR}/write.sgy WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_test( NAME python.example.makefile - COMMAND ${PYTHON_EXECUTABLE} -m examples.make-file + COMMAND ${Python_EXECUTABLE} -m examples.make-file ../test-data/large-file.sgy 20 1 20 1 20 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_test( NAME python.example.makepsfile - COMMAND ${PYTHON_EXECUTABLE} -m examples.make-ps-file + COMMAND ${Python_EXECUTABLE} -m examples.make-ps-file ../test-data/small-prestack.sgy 10 1 5 1 4 1 3 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_test( NAME python.example.subcube - COMMAND ${PYTHON_EXECUTABLE} -m examples.copy-sub-cube + COMMAND ${Python_EXECUTABLE} -m examples.copy-sub-cube ../test-data/small.sgy ${CMAKE_CURRENT_BINARY_DIR}/copy.sgy WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_test( NAME python.example.rotate - COMMAND ${PYTHON_EXECUTABLE} -m examples.make-rotated-copies + COMMAND ${Python_EXECUTABLE} -m examples.make-rotated-copies ../test-data/small.sgy ex-rotate.sgy ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_test( NAME python.example.scan_min_max - COMMAND ${PYTHON_EXECUTABLE} -m examples.scan_min_max + COMMAND ${Python_EXECUTABLE} -m examples.scan_min_max ../test-data/small.sgy WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_test( NAME python.example.multi-text - COMMAND ${PYTHON_EXECUTABLE} -m examples.make-multiple-text - _skbuild/multi-text.sgy + COMMAND ${Python_EXECUTABLE} -m examples.make-multiple-text + ${CMAKE_CURRENT_BINARY_DIR}/multi-text.sgy WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/python/pyproject.toml b/python/pyproject.toml index 73f6f749..5c97e5c3 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,10 +1,92 @@ [build-system] requires = [ - "setuptools >= 40", - "scikit-build", - "wheel", - "pybind11", + "scikit-build-core", ] +build-backend = "scikit_build_core.build" + +[project] +name = "segyio" +version = "1.9.13" +description = "Simple & fast IO for SEG-Y files" +authors = [ + { name = "Equinor ASA" }, +] +urls.homepage = "https://github.com/equinor/segyio" +license = "LGPL-3.0-or-later" +requires-python = ">=3.10" +dependencies = ["numpy >= 1.10"] +readme = { text = """ +======= +SEGY IO +======= + +https://segyio.readthedocs.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. + +Project goals +------------- + +Segyio does necessarily attempt to be the end-all of SEG-Y interactions; +rather, we aim to lower the barrier to interacting with SEG-Y files for +embedding, new applications or free-standing programs. + +Additionally, the aim is not to support the full standard or all exotic (but +correctly) formatted files out there. Some assumptions are made, such as: + + * All traces in a file are assumed to be of the same sample size. + * It is assumed all lines have the same number of traces. + +The writing functionality in Segyio is largely meant to *modify* or adapt +files. A file created from scratch is not necessarily a to-spec SEG-Y file, as +we only necessarily write the header fields segyio needs to make sense of the +geometry. It is still highly recommended that SEG-Y files are maintained and +written according to specification, but segyio does not mandate this. +""", content-type = "text/markdown" } + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", +] + + +[tool.pytest.ini_options] +python_files = "test/*.py" +addopts = "--ignore=test/benchmarks.py --ignore=test/large.py --ignore=test/stream.py" + +[tool.scikit-build] +# We can safely pass OSX_DEPLOYMENT_TARGET as it's ignored on +# everything not OS X. We depend on C++11, which makes our minimum +# supported OS X release 10.9 +cmake.args = [ "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.9" ] [tool.cibuildwheel] before-build = [ @@ -27,34 +109,13 @@ before-build = [ test-requires = "pytest" -# Copy out test folder to make sure that tests are runned against the wheel and -# not the source. Test assumes a relative path to the test-data directory, so +# Copy out test folder to make sure that tests are run against the wheel and +# not the source. The tests assume a relative path to the test-data directory, so # copy that too. segyio's python test-suite doesn't follow common naming -# convensions for filenames. Hence pytest doesn't pick them up by default. +# conventions for filenames. Hence pytest doesn't pick them up by default. # If not for powershell on windows we could simply run 'pytest test/*.py'. -# Instead we copy setup.cfg which embeds the same pattern but doesn't rely on -# the shell. -test-command = [ - """python {project}/.github/utils.py copy -r \ - --src {package}/test \ - --dst tmptest/python/test \ - """, - """python {project}/.github/utils.py copy -r \ - --src {project}/test-data \ - --dst tmptest/test-data \ - """, - """python {project}/.github/utils.py copy \ - --src {package}/setup.cfg \ - --dst tmptest/python/setup.cfg \ - """, - """cd tmptest/python""", - """pytest test""", - """cd ../../""", - """python {project}/.github/utils.py remove --paths tmptest""", -] - -[tool.cibuildwheel.linux] -manylinux-x86_64-image = "manylinux2014" +test-command = [ "cd python", "pytest test" ] +test-sources = [ "python/test", "python/pyproject.toml", "test-data" ] [tool.cibuildwheel.macos] before-build = [ @@ -66,6 +127,7 @@ before-build = [ -DBUILD_TESTING=OFF \ -DBUILD_PYTHON=OFF \ -DBUILD_BIN=OFF \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9 \ """, """sudo cmake \ --build build \ @@ -74,4 +136,3 @@ before-build = [ --config Release \ """, ] -environment = { CXXFLAGS="-L/usr/local/lib" } diff --git a/python/requirements-dev.txt b/python/requirements-dev.txt new file mode 100644 index 00000000..3d81def5 --- /dev/null +++ b/python/requirements-dev.txt @@ -0,0 +1,3 @@ +numpy +pytest +pytest-benchmark diff --git a/python/segyio/segyio.cpp b/python/segyio/segyio.cpp index 6fb32791..eb29aace 100644 --- a/python/segyio/segyio.cpp +++ b/python/segyio/segyio.cpp @@ -22,6 +22,13 @@ #define IS_PY3K #endif +#if defined(__GNUC__) && !defined(__clang__) +#define IS_GCC +#endif +#if defined(__clang__) +#define IS_CLANG +#endif + namespace { std::string segy_errstr( int err ) { @@ -98,8 +105,8 @@ PyObject* RuntimeError( int err ) { template< typename T1, typename T2 > PyObject* RuntimeError( const char* msg, T1 t1, T2 t2 ) { return PyErr_Format( PyExc_RuntimeError, msg, t1, t2 ); -} - +} + PyObject* IOErrno() { return PyErr_SetFromErrno( PyExc_IOError ); } @@ -232,6 +239,7 @@ struct buffer_guard { namespace fd { int init( segyiofd* self, PyObject* args, PyObject* kwargs ) { + (void)kwargs; // required by signature. mark to silence -Wunused-parameter char* filename = NULL; char* mode = NULL; int endian = 0; @@ -845,7 +853,7 @@ struct metrics_errmsg { "or offset (%i) field", il, xl, of ); case SEGY_INVALID_SORTING: - return RuntimeError( "unable to find sorting." + return RuntimeError( "unable to find sorting." "Check iline, (%i) and xline (%i) " "in case you are sure the file is " "a 3D sorted volume", il, xl); @@ -1336,6 +1344,16 @@ PyObject* rotation( segyiofd* self, PyObject* args ) { return PyFloat_FromDouble( rotation ); } +#ifdef IS_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif +#ifdef IS_GCC +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif PyMethodDef methods [] = { { "segyopen", (PyCFunction) fd::segyopen, METH_NOARGS, "Open file." }, { "segymake", (PyCFunction) fd::segycreate, @@ -1376,9 +1394,24 @@ PyMethodDef methods [] = { { NULL } }; +#ifdef IS_GCC +#pragma GCC diagnostic pop +#endif +#ifdef IS_CLANG +#pragma clang diagnostic pop +#endif } + +#ifdef IS_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif +#ifdef IS_GCC +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif PyTypeObject Segyiofd = { PyVarObject_HEAD_INIT( NULL, 0 ) "_segyio.segyfd", /* name */ @@ -1417,6 +1450,12 @@ PyTypeObject Segyiofd = { 0, /* tp_dictoffset */ (initproc)fd::init, /* tp_init */ }; +#ifdef IS_GCC +#pragma GCC diagnostic pop +#endif +#ifdef IS_CLANG +#pragma clang diagnostic pop +#endif PyObject* binsize( PyObject* ) { return PyLong_FromLong( segy_binheader_size() ); @@ -1583,6 +1622,16 @@ PyObject* format( PyObject* , PyObject* args ) { return out; } +#ifdef IS_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif +#ifdef IS_GCC +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif PyMethodDef SegyMethods[] = { { "binsize", (PyCFunction) binsize, METH_NOARGS, "Size of the binary header." }, { "thsize", (PyCFunction) thsize, METH_NOARGS, "Size of the trace header." }, @@ -1599,11 +1648,25 @@ PyMethodDef SegyMethods[] = { { NULL } }; +#ifdef IS_GCC +#pragma GCC diagnostic pop +#endif +#ifdef IS_CLANG +#pragma clang diagnostic pop +#endif } /* module initialization */ #ifdef IS_PY3K +#ifdef IS_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#endif +#ifdef IS_GCC +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif static struct PyModuleDef segyio_module = { PyModuleDef_HEAD_INIT, "_segyio", /* name of module */ @@ -1611,6 +1674,12 @@ static struct PyModuleDef segyio_module = { -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ SegyMethods }; +#ifdef IS_GCC +#pragma GCC diagnostic pop +#endif +#ifdef IS_CLANG +#pragma clang diagnostic pop +#endif PyMODINIT_FUNC PyInit__segyio(void) { diff --git a/python/segyio/tools.py b/python/segyio/tools.py index 50179799..d5a31f49 100644 --- a/python/segyio/tools.py +++ b/python/segyio/tools.py @@ -200,7 +200,7 @@ def collect(itr): >>> with segyio.open('post-stack.sgy') as f: >>> x = segyio.tools.collect(f.trace[:]) - >>> x = x.reshape((len(f.ilines), len(f.xlines), f.samples)) + >>> x = x.reshape((len(f.ilines), len(f.xlines), len(f.samples))) >>> numpy.all(x == segyio.tools.cube(f)) """ @@ -260,7 +260,7 @@ def rotation(f, line = 'fast'): Parameters ---------- - f : SegyFile + f : segyio.SegyFile line : { 'fast', 'slow', 'iline', 'xline' } Returns @@ -380,7 +380,7 @@ def resample(f, rate = None, delay = None, micro = False, Parameters ---------- - f : SegyFile + f : segyio.SegyFile rate : int delay : int micro : bool diff --git a/python/segyio/trace.py b/python/segyio/trace.py index d560ce26..32f9ccbc 100644 --- a/python/segyio/trace.py +++ b/python/segyio/trace.py @@ -435,8 +435,14 @@ def flush(self): be useful in certain contexts to provide stronger guarantees. """ garbage = [] + # If there are no external references to the data (so only internal + # references remain), the reference count is + # - 2 (Python >= 3.14) + # - 3 (Python < 3.14) + garbage_threshold = 3 if sys.version_info < (3, 14) else 2 + for i, (x, signature) in self.refs.items(): - if sys.getrefcount(x) == 3: + if sys.getrefcount(x) == garbage_threshold: garbage.append(i) if fingerprint(x) == signature: continue diff --git a/python/setup-CMakeLists.txt b/python/setup-CMakeLists.txt deleted file mode 100644 index cc14b632..00000000 --- a/python/setup-CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.5.0) -project(segyio-python-extension LANGUAGES C CXX) - -set(CMAKE_CXX_STANDARD 11) - -find_package(PythonExtensions REQUIRED) -find_package(segyio REQUIRED) - -add_library(_segyio MODULE segyio/segyio.cpp) -python_extension_module(_segyio) -target_link_libraries(_segyio segyio::segyio) - -if (MSVC) - target_compile_options(_segyio - BEFORE - PRIVATE - /EHsc - ) -endif () - -install(TARGETS _segyio LIBRARY DESTINATION segyio) diff --git a/python/setup.cfg b/python/setup.cfg deleted file mode 100644 index 4f1281e9..00000000 --- a/python/setup.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[metadata] -version = 1.9.13 - -[aliases] -test=pytest - -[tool:pytest] -python_files = test/*.py diff --git a/python/setup.py b/python/setup.py deleted file mode 100644 index e078d835..00000000 --- a/python/setup.py +++ /dev/null @@ -1,114 +0,0 @@ -import os -import sys -import skbuild -import setuptools - -long_description = """ -======= -SEGY IO -======= - -https://segyio.readthedocs.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. - -Project goals -------------- - -Segyio does necessarily attempt to be the end-all of SEG-Y interactions; -rather, we aim to lower the barrier to interacting with SEG-Y files for -embedding, new applications or free-standing programs. - -Additionally, the aim is not to support the full standard or all exotic (but -correctly) formatted files out there. Some assumptions are made, such as: - - * All traces in a file are assumed to be of the same sample size. - * It is assumed all lines have the same number of traces. - -The writing functionality in Segyio is largely meant to *modify* or adapt -files. A file created from scratch is not necessarily a to-spec SEG-Y file, as -we only necessarily write the header fields segyio needs to make sense of the -geometry. It is still highly recommended that SEG-Y files are maintained and -written according to specification, but segyio does not mandate this. - -""" - -def src(x): - root = os.path.dirname( __file__ ) - return os.path.abspath(os.path.join(root, x)) - -if 'MAKEFLAGS' in os.environ: - # if setup.py is called from cmake, it reads and uses the MAKEFLAGS - # environment variable, which in turn gets picked up on by scikit-build. - # However, scikit-build uses make install to move the built .so to the - # right object, still in the build tree. This make invocation honours - # DESTDIR, which leads to unwanted items in the destination tree. - # - # If the MAKEFLAGS env var is set, remove DESTDIR from it. - # - # Without this: make install DESTDIR=/tmp - # /tmp/src/segyio/python/_skbuild/linux-x86_64-3.5/cmake-install/segyio/_segyio.so - # /tmp/usr/local/lib/python2.7/site-packages/segyio/_segyio.so - # - # with this the _skbuild install is gone - makeflags = os.environ['MAKEFLAGS'] - flags = makeflags.split(' ') - flags = [flag for flag in flags if not flag.startswith('DESTDIR=')] - os.environ['MAKEFLAGS'] = ' '.join(flags) - -skbuild.setup( - name = 'segyio', - description = 'Simple & fast IO for SEG-Y files', - long_description = long_description, - author = 'Equinor ASA', - author_email = 'jokva@equinor.com', - url = 'https://github.com/equinor/segyio', - packages = ['segyio', 'segyio.su'], - package_data = { 'segyio': ['segyio.dll'], }, - license = 'LGPL-3.0', - platforms = 'any', - install_requires = ['numpy >= 1.10'], - setup_requires = [ - 'setuptools >= 28', - 'scikit-build', - ], - cmake_args = [ - # we can safely pass OSX_DEPLOYMENT_TARGET as it's ignored on - # everything not OS X. We depend on C++11, which makes our minimum - # supported OS X release 10.9 - '-DCMAKE_OSX_DEPLOYMENT_TARGET=10.9', - ], - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Other Environment', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', - 'Natural Language :: English', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Software Development :: Libraries', - 'Topic :: Utilities' - ], -) diff --git a/python/test/benchmarks.py b/python/test/benchmarks.py new file mode 100644 index 00000000..8758ad1f --- /dev/null +++ b/python/test/benchmarks.py @@ -0,0 +1,183 @@ +# Benchmarking suite, not run by default with pytest. +# To run this file pytest-benchmark must be installed via pip. + +import os +import segyio +import pytest +import numpy as np + + +# Files are expected to be present at the run location +# Files size should be compatible with retrieved lines +read_files = [ + 'file.sgy', + 'file-small.sgy', +] + +write_files = [ + 'file-small.sgy', +] + + +def run(filepath, mmap, func, mode="r"): + with segyio.open(filepath, mode=mode) as f: + if mmap: + f.mmap() + func(f) + + +def iline_slice(f): + f.iline[200] + + +def iline_strided(f): + list(f.iline[0:400:4]) + + +def xline_slice(f): + f.xline[300] + + +def xline_strided(f): + list(f.xline[0:400:4]) + + +def depth_slice(f): + f.depth_slice[400] + + +def depth_strided(f): + list(f.depth_slice[300:310:4]) + + +def reverse_traces(f): + f.trace.raw[::-100] + + +def sparse_samples(f): + list(f.trace[::3, ::4]) + + +def binary_header(f): + f.bin + + +def trace_header(f): + list(f.header[100:200:2]) + + +def attributes(f): + list(f.attributes(segyio.TraceField.INLINE_3D)) + + +def cube(filepath): + segyio.tools.cube(filepath) + + +def update_by_iline(f): + for i in f.ilines: + f.iline[i] = 2 * f.iline[i] + + +def update_by_xline(f): + for i in f.xlines: + f.xline[i] = 4 * f.xline[i] + + +def update_by_depth(f): + f.depth_slice[20:30:5] = np.ones( + (2, len(f.ilines), len(f.xlines)), dtype=np.float32 + ) + + +def update_by_traces(f): + new = [trace * 5.0 for trace in f.trace.raw[:]] + for i in range(len(f.trace)): + f.trace[i] = new[i] + + +def create(output_file): + spec = segyio.spec() + + spec.sorting = 2 + spec.format = 1 + spec.samples = range(1000) + spec.ilines = range(600) + spec.xlines = range(600) + + with segyio.create(output_file, spec) as f: + ref = np.ones((len(f.ilines), len(f.xlines)), dtype=np.float32) + tr = 0 + for il in spec.ilines: + for xl in spec.xlines: + f.header[tr] = { + segyio.su.offset: 1, + segyio.su.iline: il, + segyio.su.xline: xl + } + f.trace[tr] = ref + tr += 1 + + +operations = [ + iline_slice, + iline_strided, + xline_slice, + xline_strided, + depth_slice, + depth_strided, + reverse_traces, + sparse_samples, + binary_header, + trace_header, + attributes +] + +write_operations = [ + update_by_iline, + update_by_xline, + update_by_depth, + update_by_traces, +] + + +@pytest.mark.benchmark(group="nommap") +@pytest.mark.parametrize("file", read_files) +@pytest.mark.parametrize("func", operations) +def test_read_speed(benchmark, file, func): + benchmark(run, file, False, func) + + +@pytest.mark.benchmark(group="with mmap") +@pytest.mark.parametrize("file", read_files) +@pytest.mark.parametrize("func", operations) +def test_mmap_read_speed(benchmark, file, func): + benchmark(run, file, True, func) + + +@pytest.mark.benchmark(group="cube") +@pytest.mark.parametrize("file", read_files) +def test_cube_speed(benchmark, file): + benchmark.pedantic(run, rounds=5, args=[file, False, cube]) + + +@pytest.mark.benchmark(group="write") +@pytest.mark.parametrize("file", write_files) +@pytest.mark.parametrize("func", write_operations) +def test_write_file(benchmark, file, func): + # note that original file will get overwritten + benchmark.pedantic(run, rounds=7, args=[file, False, func, "r+"]) + + +@pytest.mark.benchmark(group="create") +def test_create_file(benchmark, tmp_path): + output_file = tmp_path / 'new.sgy' + + def setup(): + if os.path.exists(output_file): + os.remove(output_file) + + benchmark.pedantic(create, setup=setup, rounds=5, args=[output_file]) + + if os.path.exists(output_file): + os.remove(output_file) diff --git a/python/test/large.py b/python/test/large.py new file mode 100644 index 00000000..fb6422a8 --- /dev/null +++ b/python/test/large.py @@ -0,0 +1,34 @@ +import tempfile +import numpy as np +import segyio + +# requires Python >3.12 + + +def test_reading_past_4GB(): + # reading from position that can't be stored as 32 bit int + with tempfile.NamedTemporaryFile(delete_on_close=False) as temp_file: + temp_path = temp_file.name + + # each trace is 4 bytes * 250 samples = 1000 bytes + # 5 million traces = 5 GB + fmt = segyio.SegySampleFormat.IEEE_FLOAT_4_BYTE + nsamples = 250 + trace_count = 5000000 + + spec = segyio.spec() + spec.format = fmt + spec.samples = range(nsamples) + spec.tracecount = trace_count + + zero_trace = np.zeros(nsamples, dtype=np.float32) + last_trace = np.arange(1, nsamples + 1, dtype=np.float32) + + with segyio.create(temp_path, spec) as f: + for i in range(trace_count - 1): + f.trace[i] = zero_trace + + f.trace[trace_count - 1] = last_trace + + with segyio.open(temp_path, "r", strict=False) as f: + assert np.array_equal(f.trace[trace_count - 1], last_trace) diff --git a/python/test/segy.py b/python/test/segy.py index e86afcd0..3f4faa37 100644 --- a/python/test/segy.py +++ b/python/test/segy.py @@ -1073,6 +1073,21 @@ def test_assign_all_traces(small): with segyio.open(copy) as f: assert np.array_equal(f.trace.raw[:], traces) + with segyio.open(copy, 'r+') as f: + traces = f.trace.raw[:] + f.trace[::2] = [trace * 2.0 for trace in traces[::2]] + f.trace[1::2] = [trace * 3.0 for trace in traces[1::2]] + + with segyio.open(copy) as f: + for index, trace in enumerate(f.trace): + # assuming precision error comes from ibm-iee conversion as file is ibm + if index % 2 == 0: + npt.assert_array_almost_equal( + trace, traces[index] * 2.0, decimal=5) + else: + npt.assert_array_almost_equal( + trace, traces[index] * 3.0, decimal=5) + def test_traceaccess_from_array(): a = np.arange(10, dtype=int) @@ -1720,6 +1735,16 @@ def value(x, y): assert np.allclose(depth_slice, buf * index) next(islice(itr, 3, 3), None) + other = [buf * i * 2 for i in range(len(f.depth_slice))] + f.depth_slice[::2] = other[::2] + + itr = iter(enumerate(f.depth_slice)) + for index, depth_slice in itr: + if index % 2 == 0: + assert np.allclose(depth_slice, buf * index * 2) + else: + assert np.allclose(depth_slice, buf * index) + @pytest.mark.parametrize('endian', ['little', 'big']) def test_no_16bit_overflow_tracecount(endian, tmpdir): diff --git a/python/test/segyio_c.py b/python/test/segyio_c.py index b1e144d9..2e1231f1 100644 --- a/python/test/segyio_c.py +++ b/python/test/segyio_c.py @@ -588,4 +588,3 @@ def test_fread_trace0_for_depth(): with pytest.raises(KeyError): _segyio.fread_trace0(25, 1, 1, 1, indices, "depth") - diff --git a/test-data/README.md b/test-data/README.md new file mode 100644 index 00000000..d1e8fda9 --- /dev/null +++ b/test-data/README.md @@ -0,0 +1,81 @@ +# Test data + +Test files names are already self-telling, but this overview aims to provide more context. + +Due to changes in the scripts after the files were originally created, reproducing might not give 100% same result, but similar enough. + +## General test files + +| File | Purpose | Recreation | Comment | +|--------------------------------|-----------------------------------------------------------------------------|-------------------------------------------------------|--------------------------------------------| +| 1x1.sgy | File with 1 inline, 1 xline and 4 offsets - 4 traces of 10 samples total | make-ps-file.py 1x1.sgy 10 0 1 0 1 0 4 | | +| 1xN.sgy | File with 1 inline, 6 xlines and 4 offsets - 24 traces of 10 samples total | make-ps-file.py 1xN.sgy 10 0 1 0 6 0 4 | | +| Mx1.sgy | File with 6 inlines, 1 xline and 4 offsets - 24 traces of 10 samples total | make-ps-file.py Mx1.sgy 10 0 1 0 1 0 4 | | +| delay-scalar.sgy | Recording time delay is 10000 and corresponding scalar is -10. | likely real file, cropped | ASCII text header | +| f3.sgy | File in format number 3 (2-byte, two's complement integers). | cropped real file | see text header | +| f3-lsb.sgy | Same as f3.sgy, but little-endian. | ./flip-endianness --samples 75 -F 2 f3.sgy f3-lsb.sgy | | +| interval-neg-bin-neg-trace.sgy | Sample interval in binary header is -2000 and same in trace is -25000. | | 1 sample, 1 trace | +| interval-neg-bin-pos-trace.sgy | Sample interval in binary header is -2 and same in trace is 4000. | | 1 sample, 1 trace | +| interval-pos-bin-neg-trace.sgy | Sample interval in binary header is 2000 and same in trace is -25000. | | 1 sample, 1 trace | +| long.sgy | Declares 60000 samples (value is written in 16-bit field in binary header). | | broken as proper trace headers are missing | +| multi-text.sgy | Contains 4 extended text headers | python make-multiple-text.py multi-text.sgy | 1 sample, 1 trace | +| shot-gather.sgy | Some traces have common field record [2 3 5 8]/geophone group [1 2]. | make-shot-gather.py shot-gather.sgy | First trace value is field record value. | +| small-lsb.sgy | small.sgy converted to little endian | ./flip-endianness --samples 50 small.sgy small-ps.sgy | | +| small-ps.sgy | Pre-stack file with 2 offsets. | python make-ps-file.py small-ps.sgy 10 1 5 1 4 1 3 | | +| small.sgy | Inline-sorted file with 50 samples, 5 inlines-xlines test file. | make-file.py small.sgy 50 1 6 20 25 | Basic file used in testing | +| text-embed-null.sgy | small.sgy but with one byte in text header exchanged with 00 | | | +| text.sgy | File with unique textual file header. | | no traces | +| 小文件.sgy | small.sgy but with utf-8 characters in the filename | rename small.sgy | bug test for Windows OS | + + +## Rotation test files + +Shows how survey coordinate system relates to cdp. Described angles are relative to starting position, clock-wise. + +To recreate: +`python python/examples/make-rotated-copies.py test-data/small.sgy small.sgy test-data` + + +| File | Purpose | +|---------------------|-----------------------------------------------------------------------| +| normal-small.sgy | 0° Survey coordinate system is aligned to cdpx/cdpy coordinate system | +| acute-small.sgy | 45° | +| right-small.sgy | 90° | +| obtuse-small.sgy | 135° | +| straight-small.sgy | 180° | +| reflex-small.sgy | 225° | +| left-small.sgy | 270° | +| inv-acute-small.sgy | 315° | + + +## Dimensions sorting test files + +Could be reproduced with +`python python/examples/sorting-permutation.py test-data/small-ps.sgy` + +Dimensions are mentioned in the order from slowest to fastest changing. + +| File | Purpose | +|--------------------------------|-------------------------------------------------------------| +| small-ps-dec-il-inc-xl-off.sgy | Inlines: decreasing. Xlines: increasing. Offset: increasing | +| small-ps-dec-il-off-inc-xl.sgy | Inlines: decreasing. Xlines: increasing. Offset: decreasing | +| small-ps-dec-il-xl-inc-off.sgy | Inlines: decreasing. Xlines: decreasing. Offset: increasing | +| small-ps-dec-il-xl-off.sgy | Inlines: decreasing. Xlines: decreasing. Offset: decreasing | +| small-ps-dec-off-inc-il-xl.sgy | Inlines: increasing. Xlines: increasing. Offset: decreasing | +| small-ps-dec-xl-inc-il-off.sgy | Inlines: increasing. Xlines: decreasing. Offset: increasing | +| small-ps-dec-xl-off-inc-il.sgy | Inlines: increasing. Xlines: decreasing. Offset: decreasing | + + +## Seismic Un*x format + +| File | Purpose | Recreation | +|--------------|-------------------------------------------------------------------------|-----------------------------------------------------------------------------------------| +| small-lsb.su | small.sgy converted to Seismic Un*x format and swapped to little endian | suswapbytes < small.su > small-lsb.su | +| small.su | small.sgy converted to Seismic Un*x format | segyread tape=small.sgy ns=50 remap=tracr,cdp byte=189l,193l conv=1 format=1 > small.su | + + +## Multiformat + +Files in the `multiformat` directory. + +`f3.sgy` converted to multiple formats, both big/little endian. Creations methods vary/unknown. diff --git a/test-data/inv-small-ps.sgy b/test-data/inv-small-ps.sgy deleted file mode 100644 index 15badc2d..00000000 Binary files a/test-data/inv-small-ps.sgy and /dev/null differ diff --git a/test-data/multiformats/Format15lsb.sgy b/test-data/multiformats/Format15lsb.sgy index 9addbf6e..27c333af 100644 Binary files a/test-data/multiformats/Format15lsb.sgy and b/test-data/multiformats/Format15lsb.sgy differ diff --git a/test-data/multiformats/Format15msb.sgy b/test-data/multiformats/Format15msb.sgy index 8c925a0c..1e9c6f09 100644 Binary files a/test-data/multiformats/Format15msb.sgy and b/test-data/multiformats/Format15msb.sgy differ diff --git a/test-data/multiformats/Format7lsb.sgy b/test-data/multiformats/Format7lsb.sgy index 0625b51b..c4811408 100644 Binary files a/test-data/multiformats/Format7lsb.sgy and b/test-data/multiformats/Format7lsb.sgy differ diff --git a/test-data/multiformats/Format7msb.sgy b/test-data/multiformats/Format7msb.sgy index e2d21828..64e4e49f 100644 Binary files a/test-data/multiformats/Format7msb.sgy and b/test-data/multiformats/Format7msb.sgy differ