Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,7 +29,7 @@ dependencies = [
"segy (>=0.4.2,<0.5.0)",
"tqdm (>=4.67.1,<5.0.0)",
"xarray>=2025.7.1",
"zarr (>=3.1.1,<4.0.0)",
"zarr (>=3.1.2,<4.0.0)",
]

[project.optional-dependencies]
Expand Down
48 changes: 21 additions & 27 deletions src/mdio/converters/segy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -163,7 +163,7 @@ def _build_and_check_grid(segy_dimensions: list[Dimension], segy_file: SegyFile,


def _get_coordinates(
segy_dimensions: list[Dimension],
grid: Grid,
segy_headers: SegyHeaderArray,
mdio_template: AbstractDatasetTemplate,
) -> tuple[list[Dimension], dict[str, SegyHeaderArray]]:
Expand All@@ -174,7 +174,7 @@ def _get_coordinates(
The last dimension is always the vertical domain dimension

Args:
segy_dimensions: List of of all SEG-Y dimensions.
grid: Inferred MDIO grid for SEG-Y file.
segy_headers: Headers read in from SEG-Y file.
mdio_template: The MDIO template to use for the conversion.

Expand All@@ -188,19 +188,15 @@ def _get_coordinates(
- A dict of non-dimension coordinates (str: N-D arrays).
"""
dimensions_coords = []
dim_names = [dim.name for dim in segy_dimensions]
for dim_name in mdio_template.dimension_names:
try:
dim_index = dim_names.index(dim_name)
except ValueError:
if dim_name not in grid.dim_names:
err = f"Dimension '{dim_name}' was not found in SEG-Y dimensions."
raise ValueError(err) from err
dimensions_coords.append(segy_dimensions[dim_index])
raise ValueError(err)
dimensions_coords.append(grid.select_dim(dim_name))

non_dim_coords: dict[str, SegyHeaderArray] = {}
available_headers = segy_headers.dtype.names
for coord_name in mdio_template.coordinate_names:
if coord_name not in available_headers:
if coord_name not in segy_headers.dtype.names:
err = f"Coordinate '{coord_name}' not found in SEG-Y dimensions."
raise ValueError(err)
non_dim_coords[coord_name] = segy_headers[coord_name]
Expand All@@ -227,12 +223,14 @@ def populate_non_dim_coordinates(
"""Populate the xarray dataset with coordinate variables."""
not_null = grid.map[:] != UINT32_MAX
for c_name, c_values in coordinates.items():
dataset[c_name].values[not_null] = c_values
c_tmp_array = dataset[c_name].values
c_tmp_array[not_null] = c_values
dataset[c_name][:] = c_tmp_array
drop_vars_delayed.append(c_name)
return dataset, drop_vars_delayed


def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> LengthUnitEnum | None:
def _get_horizontal_coordinate_unit(segy_headers: list[Dimension]) -> AllUnits | None:
"""Get the coordinate unit from the SEG-Y headers."""
name = TraceHeaderFieldsRev0.COORDINATE_UNIT.name.upper()
unit_hdr = next((c for c in segy_headers if c.name.upper() == name), None)
Expand DownExpand Up@@ -347,15 +345,17 @@ def segy_to_mdio(

grid = _build_and_check_grid(segy_dimensions, segy_file, segy_headers)

dimensions, non_dim_coords = _get_coordinates(segy_dimensions, segy_headers, mdio_template)
shape = [len(dim.coords) for dim in dimensions]
dimensions, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template)
# TODO(Altay): Turn this dtype into packed representation
# https://github.com/TGSAI/mdio-python/issues/601
headers = to_structured_type(segy_spec.trace.header.dtype)

horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions)
mdio_ds: Dataset = mdio_template.build_dataset(
name=mdio_template.name, sizes=shape, horizontal_coord_unit=horizontal_unit, headers=headers
name=mdio_template.name,
sizes=grid.shape,
horizontal_coord_unit=horizontal_unit,
headers=headers,
)

_add_text_binary_headers(dataset=mdio_ds, segy_file=segy_file)
Expand All@@ -376,18 +376,12 @@ def segy_to_mdio(
# IMPORTANT: Do not drop the "trace_mask" here, as it will be used later in
# blocked_io.to_zarr() -> _workers.trace_worker()

# Write the xarray dataset to Zarr with as following:
# Populated arrays:
# - 1D dimensional coordinates
# - ND non-dimensional coordinates
# - ND trace_mask
# Empty arrays (will be populated later in chunks):
# - ND+1 traces
# - ND headers (no _FillValue set due to the bug https://github.com/TGSAI/mdio-python/issues/582)
# This will create the Zarr store with the correct structure
# TODO(Dmitriy Repin): do chunked write for non-dimensional coordinates and trace_mask
# https://github.com/TGSAI/mdio-python/issues/587
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=True)
# This will create the Zarr store with the correct structure but with empty arrays
xr_dataset.to_zarr(store=output_location.uri, mode="w", write_empty_chunks=False, zarr_format=2, compute=False)

# This will write the non-dimension coordinates and trace mask
meta_ds = xr_dataset[drop_vars_delayed + ["trace_mask"]]
meta_ds.to_zarr(store=output_location.uri, mode="r+", write_empty_chunks=False, zarr_format=2, compute=True)

# Now we can drop them to simplify chunked write of the data variable
xr_dataset = xr_dataset.drop_vars(drop_vars_delayed)
Expand Down
11 changes: 5 additions & 6 deletions src/mdio/schemas/v1/dataset_serializer.py
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
"""Convert MDIO v1 schema Dataset to Xarray DataSet and write it in Zarr."""

import numpy as np
from dask import array as dask_array
from numcodecs import Blosc as nc_Blosc
from xarray import DataArray as xr_DataArray
from xarray import Dataset as xr_Dataset
from zarr import zeros as zarr_zeros
from zarr.core.chunk_key_encodings import V2ChunkKeyEncoding

from mdio.converters.type_converter import to_numpy_dtype
Expand DownExpand Up@@ -177,8 +177,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
mdio_ds: The source MDIO dataset to construct from.

Notes:
- We can't use Dask (e.g., dask_array.zeros) because of the problems with
structured type support. We will uze zarr.zeros instead
- Using dask.array.zeros for lazy evaluation to prevent eager memory allocation
while maintaining support for structured dtypes

Returns:
The constructed dataset with proper MDIO structure and metadata.
Expand All@@ -195,9 +195,8 @@ def to_xarray_dataset(mdio_ds: Dataset) -> xr_Dataset: # noqa: PLR0912
dtype = to_numpy_dtype(v.data_type)
chunks = _get_zarr_chunks(v, all_named_dims=all_named_dims)

# Use zarr.zeros to create an empty array with the specified shape and dtype
# NOTE: zarr_format=2 is essential, to_zarr() will fail if zarr_format=2 is used
data = zarr_zeros(shape=shape, dtype=dtype, zarr_format=2)
# Use dask.array.zeros to create a lazy array
data = dask_array.full(shape=shape, dtype=dtype, chunks=chunks, fill_value=_get_fill_value(v.data_type))
# Create a DataArray for the variable. We will set coords in the second pass
dim_names = _get_dimension_names(v)
data_array = xr_DataArray(data, dims=dim_names)
Expand Down
40 changes: 24 additions & 16 deletions src/mdio/segy/_workers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@
import numpy as np
from segy import SegyFile

from mdio.schemas import ScalarType

if TYPE_CHECKING:
from segy.arrays import HeaderArray
from segy.config import SegySettings
Expand All@@ -19,7 +21,9 @@

from mdio.core.storage_location import StorageLocation


from mdio.constants import UINT32_MAX
from mdio.schemas.v1.dataset_serializer import _get_fill_value
from mdio.schemas.v1.stats import CenteredBinHistogram
from mdio.schemas.v1.stats import SummaryStatistics

Expand DownExpand Up@@ -109,33 +113,37 @@ def trace_worker( # noqa: PLR0913
live_trace_indexes = grid_map[not_null].tolist()
traces = segy_file.trace[live_trace_indexes]

header_key = "headers"

# Get subset of the dataset that has not yet been saved
# The headers might not be present in the dataset
# TODO(Dmitriy Repin): Check, should we overwrite the 'dataset' instead to save the memory
# https://github.com/TGSAI/mdio-python/issues/584
if "headers" in dataset.data_vars:
ds_to_write = dataset[[data_variable_name, "headers"]]
ds_to_write = ds_to_write.reset_coords()

ds_to_write["headers"].data[not_null] = traces.header
ds_to_write["headers"].data[~not_null] = 0
else:
ds_to_write = dataset[[data_variable_name]]
ds_to_write = ds_to_write.reset_coords()
worker_variables = [data_variable_name]
if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations
worker_variables.append(header_key)

ds_to_write = dataset[worker_variables]

if header_key in worker_variables:
# Create temporary array for headers with the correct shape
# TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code. #noqa: TD003
tmp_headers = np.zeros_like(dataset[header_key])
tmp_headers[not_null] = traces.header
ds_to_write[header_key][:] = tmp_headers

ds_to_write[data_variable_name].data[not_null] = traces.sample
data_variable = ds_to_write[data_variable_name]
fill_value = _get_fill_value(ScalarType(data_variable.dtype.name))
tmp_samples = np.full_like(data_variable, fill_value=fill_value)
tmp_samples[not_null] = traces.sample
ds_to_write[data_variable_name][:] = tmp_samples

out_path = output_location.uri
ds_to_write.to_zarr(out_path, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)
ds_to_write.to_zarr(output_location.uri, region=region, mode="r+", write_empty_chunks=False, zarr_format=2)

histogram = CenteredBinHistogram(bin_centers=[], counts=[])
return SummaryStatistics(
count=traces.sample.size,
min=traces.sample.min(),
max=traces.sample.max(),
sum=traces.sample.sum(),
# TODO(Altay): Look at how to do the sum squares statistic correctly
# https://github.com/TGSAI/mdio-python/issues/581
sum_squares=(traces.sample**2).sum(),
histogram=histogram,
)
1 change: 0 additions & 1 deletion src/mdio/segy/blocked_io.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -98,7 +98,6 @@ def to_zarr( # noqa: PLR0913, PLR0915
num_workers = min(num_chunks, num_cpus)
context = mp.get_context("spawn")
executor = ProcessPoolExecutor(max_workers=num_workers, mp_context=context)
# return executor

segy_kw = {
"url": segy_file.fs.unstrip_protocol(segy_file.url),
Expand Down
Loading
Loading