Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds greater shot ingestions flexibility #213

Merged
merged 15 commits into from
Jun 6, 2023
Merged
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[flake8]
select = B,B9,C,D,DAR,E,F,N,RST,S,W
ignore = E203,E501,RST201,RST203,RST301,W503
max-line-length = 80
max-line-length = 88
max-complexity = 10
docstring-convention = google
rst-roles = class,const,func,meth,mod,ref
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ docs/_build/
src/*.egg-info/
__pycache__/
.idea/
*venv*
49 changes: 49 additions & 0 deletions src/mdio/commands/segy.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@
type=click.BOOL,
show_default=True,
)
@click.option(
"-grid-overrides",
"--grid-overrides",
required=False,
help="Option to add grid overrides.",
type=click_params.JSON,
)
def segy_import(
input_segy_path,
output_mdio_file,
Expand All @@ -159,6 +166,7 @@ def segy_import(
compression_tolerance,
storage_options,
overwrite,
grid_overrides,
):
"""Ingest SEG-Y file to MDIO.

Expand Down Expand Up @@ -261,6 +269,46 @@ def segy_import(
--header-names shot,cable,chan
--header-lengths 4,2,4
--chunk-size 8,2,256,512

We can override the dataset grid by the `grid_overrides` parameter.
This allows us to ingest files that don't conform to the true
geometry of the seismic acquisition.

For example if we are ingesting 3D seismic shots that don't have
a cable number and channel numbers are sequential (i.e. each cable
doesn't start with channel number 1; we can tell MDIO to ingest
this with the correct geometry by calculating cable numbers and
wrapped channel numbers. Note the missing byte location and word
length for the "cable" index.


Usage:
3D Seismic Shot Data (Byte Locations Vary):
Let's assume streamer number does not exist but there are
800 channels per cable.
Chunks: 8 shots x 2 cables x 256 channels x 512 samples
--header-locations 9,None,13
--header-names shot,cable,chan
--header-lengths 4,None,4
--chunk-size 8,2,256,512
--grid-overrides '{"ChannelWrap": True, "ChannelsPerCable": 800,
"CalculateCable": True}'

\b
If we do have cable numbers in the headers, but channels are still
sequential (aka. unwrapped), we can still ingest it like this.
--header-locations 9,213,13
--header-names shot,cable,chan
--header-lengths 4,2,4
--chunk-size 8,2,256,512
--grid-overrides '{"ChannelWrap":True, "ChannelsPerCable": 800}'
\b
For shot gathers with channel numbers and wrapped channels, no
grid overrides are necessary.

In cases where the user does not know if the input has unwrapped
channels but desires to store with wrapped channel index use:
--grid-overrides '{"AutoChannelWrap": True}'
"""
mdio.segy_to_mdio(
segy_path=input_segy_path,
Expand All @@ -274,6 +322,7 @@ def segy_import(
compression_tolerance=compression_tolerance,
storage_options=storage_options,
overwrite=overwrite,
grid_overrides=grid_overrides,
)


Expand Down
5 changes: 5 additions & 0 deletions src/mdio/converters/segy.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ def segy_to_mdio(

For shot gathers with channel numbers and wrapped channels, no
grid overrides are necessary.

In cases where the user does not know if the input has unwrapped
channels but desires to store with wrapped channel index use:
>>> grid_overrides={"AutoChannelWrap": True,
"AutoChannelTraceQC": 1000000}
"""
num_index = len(index_bytes)

Expand Down
8 changes: 5 additions & 3 deletions src/mdio/segy/_workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ def header_scan_worker(
segy_endian: Endianness of the input SEG-Y. Rev.2 allows little endian

Returns:
Numpy array of parsed headers for the current block.
dictionary with headers: keys are the index names, values are numpy
arrays of parsed headers for the current block. Array is of type
byte_type with the exception of IBM32 which is mapped to FLOAT32.

Raises:
TypeError: if segy_path_or_handle is incorrect / unsupported.
Expand All @@ -59,7 +61,6 @@ def header_scan_worker(
ignore_geometry=True,
endian=segy_endian,
) as segy_handle:

block_headers = [
segy_handle.header[trc_idx] for trc_idx in range(start, stop)
]
Expand Down Expand Up @@ -109,7 +110,8 @@ def header_scan_worker(

out_dtype.append((name, native_dtype))

out_array = np.empty(n_traces, out_dtype)
# out_array = np.empty(n_traces, out_dtype)
out_array = {}

# TODO: Add strict=True and remove noqa when minimum Python is 3.10
for name, loc, type_ in zip(index_names, byte_locs, byte_types): # noqa: B905
Expand Down
47 changes: 46 additions & 1 deletion src/mdio/segy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,49 @@
class InvalidSEGYFileError(MDIOError):
"""Raised when there is an IOError from segyio."""

pass

class GridOverrideInputError(MDIOError):
"""Raised when grid override parameters are not correct."""


class GridOverrideUnknownError(GridOverrideInputError):
"""Raised when grid override parameter is unknown."""

def __init__(self, command_name):
"""Initialize with custom message."""
self.command_name = command_name
self.message = f"Unknown grid override: {command_name}"
super().__init__(self.message)


class GridOverrideKeysError(GridOverrideInputError):
"""Raised when grid override is not compatible with required keys."""

def __init__(self, command_name, required_keys):
"""Initialize with custom message."""
self.command_name = command_name
self.required_keys = required_keys
self.message = f"{command_name} can only be used with {required_keys} keys."
super().__init__(self.message)


class GridOverrideMissingParameterError(GridOverrideInputError):
"""Raised when grid override parameters are not correct."""

def __init__(self, command_name, missing_parameter):
"""Initialize with custom message."""
self.command_name = command_name
self.missing_parameter = missing_parameter
self.message = f"{command_name} requires {missing_parameter} parameter."
super().__init__(self.message)


class GridOverrideIncompatibleError(GridOverrideInputError):
"""Raised when two grid overrides are incompatible."""

def __init__(self, first_command, second_command):
"""Initialize with custom message."""
self.first_command = first_command
self.second_command = second_command
self.message = f"{first_command} can't be used together with {second_command}."
super().__init__(self.message)
Loading