From 6bb180a6ca80929d483ccfa1065977fb9b664cbf Mon Sep 17 00:00:00 2001 From: Brian Michell Date: Tue, 30 Sep 2025 06:56:57 -0700 Subject: [PATCH 01/39] Update Xarray api access (#688) --- pyproject.toml | 2 +- src/mdio/api/io.py | 2 +- uv.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4a0dd004..479a6220 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "segy>=0.5.0", "tqdm>=4.67.1", "universal-pathlib>=0.2.6", - "xarray>=2025.9.0", + "xarray>=2025.9.1", "zarr>=3.1.3", ] diff --git a/src/mdio/api/io.py b/src/mdio/api/io.py index 862c66ed..2654be31 100644 --- a/src/mdio/api/io.py +++ b/src/mdio/api/io.py @@ -10,7 +10,7 @@ from upath import UPath from xarray import Dataset as xr_Dataset from xarray import open_zarr as xr_open_zarr -from xarray.backends.api import to_zarr as xr_to_zarr +from xarray.backends.writers import to_zarr as xr_to_zarr from mdio.constants import ZarrFormat from mdio.core.zarr_io import zarr_warnings_suppress_unstable_structs_v3 diff --git a/uv.lock b/uv.lock index 1de9b17f..dc0f6c03 100644 --- a/uv.lock +++ b/uv.lock @@ -1925,7 +1925,7 @@ requires-dist = [ { name = "segy", specifier = ">=0.5.0" }, { name = "tqdm", specifier = ">=4.67.1" }, { name = "universal-pathlib", specifier = ">=0.2.6" }, - { name = "xarray", specifier = ">=2025.9.0" }, + { name = "xarray", specifier = ">=2025.9.1" }, { name = "zarr", specifier = ">=3.1.3" }, { name = "zfpy", marker = "extra == 'lossy'", specifier = ">=1.0.1" }, ] @@ -3876,16 +3876,16 @@ wheels = [ [[package]] name = "xarray" -version = "2025.9.0" +version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "packaging" }, { name = "pandas" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/0b/bbb76e05c8e2099baf90e259c29cafe6a525524b1d1da8bfbc39577c043e/xarray-2025.9.0.tar.gz", hash = "sha256:7dd6816fe0062c49c5e9370dd483843bc13e5ed80a47a9ff10baff2b51e070fb", size = 3040318, upload-time = "2025-09-04T04:20:26.296Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/5d/e139112a463336c636d4455494f3227b7f47a2e06ca7571e6b88158ffc06/xarray-2025.9.1.tar.gz", hash = "sha256:f34a27a52c13d1f3cceb7b27276aeec47021558363617dd7ef4f4c8b379011c0", size = 3057322, upload-time = "2025-09-30T05:28:53.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/f0/73c24457c941b8b08f7d090853e40f4b2cdde88b5da721f3f28e98df77c9/xarray-2025.9.0-py3-none-any.whl", hash = "sha256:79f0e25fb39571f612526ee998ee5404d8725a1db3951aabffdb287388885df0", size = 1349595, upload-time = "2025-09-04T04:20:24.36Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a7/6eeb32e705d510a672f74135f538ad27f87f3d600845bfd3834ea3a77c7e/xarray-2025.9.1-py3-none-any.whl", hash = "sha256:3e9708db0d7915c784ed6c227d81b398dca4957afe68d119481f8a448fc88c44", size = 1364411, upload-time = "2025-09-30T05:28:51.294Z" }, ] [[package]] From 1271bb7754f4de2b364f0f2760579b070f340200 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 9 Sep 2025 15:34:34 +0000 Subject: [PATCH 02/39] Reimplement disaster recovery logic --- src/mdio/builder/schemas/dtype.py | 1 + src/mdio/constants.py | 1 + src/mdio/converters/segy.py | 62 +++++++++++++++++++++++++++ src/mdio/converters/type_converter.py | 2 + src/mdio/segy/_workers.py | 13 +++++- 5 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/mdio/builder/schemas/dtype.py b/src/mdio/builder/schemas/dtype.py index 8e8cbcd3..caeb6c3b 100644 --- a/src/mdio/builder/schemas/dtype.py +++ b/src/mdio/builder/schemas/dtype.py @@ -32,6 +32,7 @@ class ScalarType(StrEnum): COMPLEX64 = "complex64" COMPLEX128 = "complex128" COMPLEX256 = "complex256" + HEADERS_V3 = "r1920" # Raw number of BITS, must be a multiple of 8 class StructuredField(CamelCaseStrictModel): diff --git a/src/mdio/constants.py b/src/mdio/constants.py index f2ddb65c..6126eada 100644 --- a/src/mdio/constants.py +++ b/src/mdio/constants.py @@ -64,4 +64,5 @@ class ZarrFormat(IntEnum): ScalarType.COMPLEX64: complex(np.nan, np.nan), ScalarType.COMPLEX128: complex(np.nan, np.nan), ScalarType.COMPLEX256: complex(np.nan, np.nan), + ScalarType.HEADERS_V3: b"\x00" * 240, } diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index cdf9f88e..06f4914d 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -22,6 +22,11 @@ from mdio.converters.exceptions import GridTraceSparsityError from mdio.converters.type_converter import to_structured_type from mdio.core.grid import Grid +from mdio.builder.schemas.chunk_grid import RegularChunkGrid +from mdio.builder.schemas.chunk_grid import RegularChunkShape +from mdio.builder.schemas.compressors import Blosc +from mdio.builder.schemas.compressors import BloscCname +from mdio.builder.schemas.dtype import ScalarType from mdio.segy import blocked_io from mdio.segy.utilities import get_grid_plan @@ -333,6 +338,58 @@ def _add_grid_override_to_metadata(dataset: Dataset, grid_overrides: dict[str, A dataset.metadata.attributes["gridOverrides"] = grid_overrides +def _add_raw_headers_to_template(mdio_template: AbstractDatasetTemplate) -> AbstractDatasetTemplate: + """Add raw headers capability to the MDIO template by monkey-patching its _add_variables method. + This function modifies the template's _add_variables method to also add a raw headers variable + with the following characteristics: + - Same rank as the Headers variable (all dimensions except vertical) + - Name: "RawHeaders" + - Type: ScalarType.HEADERS + - No coordinates + - zstd compressor + - No additional metadata + - Chunked the same as the Headers variable + Args: + mdio_template: The MDIO template to mutate + """ + # Check if raw headers enhancement has already been applied to avoid duplicate additions + if hasattr(mdio_template, '_mdio_raw_headers_enhanced'): + return mdio_template + + # Store the original _add_variables method + original_add_variables = mdio_template._add_variables + + def enhanced_add_variables() -> None: + # Call the original method first + original_add_variables() + + # Now add the raw headers variable + chunk_shape = mdio_template._var_chunk_shape[:-1] + + # Create chunk grid metadata + chunk_metadata = RegularChunkGrid(configuration=RegularChunkShape(chunk_shape=chunk_shape)) + from mdio.builder.schemas.v1.variable import VariableMetadata + + # Add the raw headers variable using the builder's add_variable method + mdio_template._builder.add_variable( + name="raw_headers", + long_name="Raw Headers", + dimensions=mdio_template._dim_names[:-1], # All dimensions except vertical + data_type=ScalarType.HEADERS_V3, + compressor=Blosc(cname=BloscCname.zstd), + coordinates=None, # No coordinates as specified + metadata=VariableMetadata(chunk_grid=chunk_metadata), + ) + + # Replace the template's _add_variables method + mdio_template._add_variables = enhanced_add_variables + + # Mark the template as enhanced to prevent duplicate monkey-patching + mdio_template._mdio_raw_headers_enhanced = True + + return mdio_template + + def segy_to_mdio( # noqa PLR0913 segy_spec: SegySpec, mdio_template: AbstractDatasetTemplate, @@ -372,6 +429,11 @@ def segy_to_mdio( # noqa PLR0913 _, non_dim_coords = _get_coordinates(grid, segy_headers, mdio_template) header_dtype = to_structured_type(segy_spec.trace.header.dtype) + + if os.getenv("MDIO__DO_RAW_HEADERS") == "1": + logger.warning("MDIO__DO_RAW_HEADERS is experimental and expected to change or be removed.") + mdio_template = _add_raw_headers_to_template(mdio_template) + horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions) mdio_ds: Dataset = mdio_template.build_dataset( name=mdio_template.name, diff --git a/src/mdio/converters/type_converter.py b/src/mdio/converters/type_converter.py index 427abdc7..eec4e99f 100644 --- a/src/mdio/converters/type_converter.py +++ b/src/mdio/converters/type_converter.py @@ -78,6 +78,8 @@ def to_structured_type(data_type: np_dtype) -> StructuredType: def to_numpy_dtype(data_type: ScalarType | StructuredType) -> np_dtype: """Get the numpy dtype for a variable.""" if isinstance(data_type, ScalarType): + if data_type == ScalarType.HEADERS_V3: + return np_dtype("|V240") return np_dtype(data_type.value) if isinstance(data_type, StructuredType): return np_dtype([(f.name, f.format.value) for f in data_type.fields]) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 17925121..55bda71d 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -124,12 +124,15 @@ def trace_worker( # noqa: PLR0913 traces = segy_file.trace[live_trace_indexes] header_key = "headers" + raw_header_key = "raw_headers" # Get subset of the dataset that has not yet been saved # The headers might not be present in the dataset 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) + if raw_header_key in dataset.data_vars: + worker_variables.append(raw_header_key) ds_to_write = dataset[worker_variables] @@ -149,7 +152,15 @@ def trace_worker( # noqa: PLR0913 attrs=ds_to_write[header_key].attrs, encoding=ds_to_write[header_key].encoding, # Not strictly necessary, but safer than not doing it. ) - + if raw_header_key in worker_variables: + tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) + tmp_raw_headers[not_null] = traces.header.view("|V240") # TODO: Ensure this is using the RAW view and not an interpreted view. + ds_to_write[raw_header_key] = Variable( + ds_to_write[raw_header_key].dims, + tmp_raw_headers, + attrs=ds_to_write[raw_header_key].attrs, + encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. + ) 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) From fb60ef53ad6c0997a5ee5a53d006e237d12360f0 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 10 Sep 2025 16:16:00 +0000 Subject: [PATCH 03/39] Ensure getting true raw bytes for DR array --- src/mdio/segy/_workers.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 55bda71d..3bf3d147 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -9,6 +9,7 @@ import numpy as np from segy import SegyFile +from segy.indexing import merge_cat_file from mdio.api.io import to_mdio from mdio.builder.schemas.dtype import ScalarType @@ -154,13 +155,39 @@ def trace_worker( # noqa: PLR0913 ) if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - tmp_raw_headers[not_null] = traces.header.view("|V240") # TODO: Ensure this is using the RAW view and not an interpreted view. + + # Get the indices where we need to place results + live_mask = not_null + live_positions = np.where(live_mask.ravel())[0] + + if len(live_positions) > 0: + # Calculate byte ranges for headers + HEADER_SIZE = 240 + trace_offset = segy_file.spec.trace.offset + trace_itemsize = segy_file.spec.trace.itemsize + + starts = [] + ends = [] + for global_trace_idx in live_trace_indexes: + header_start = trace_offset + global_trace_idx * trace_itemsize + header_end = header_start + HEADER_SIZE + starts.append(header_start) + ends.append(header_end) + + # Capture raw bytes + raw_header_bytes = merge_cat_file(segy_file.fs, segy_file.url, starts, ends) + + # Convert and place results + raw_headers_array = np.frombuffer(bytes(raw_header_bytes), dtype="|V240") + tmp_raw_headers.ravel()[live_positions] = raw_headers_array + ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, tmp_raw_headers, attrs=ds_to_write[raw_header_key].attrs, - encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. + encoding=ds_to_write[raw_header_key].encoding, ) + 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) From a2ee5b8c5a01779275e578504210b92fff65a399 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 10 Sep 2025 16:20:45 +0000 Subject: [PATCH 04/39] Linting --- src/mdio/converters/segy.py | 17 ++++++++++------- src/mdio/segy/_workers.py | 18 +++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index 06f4914d..df4c259e 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -14,19 +14,20 @@ from mdio.api.io import _normalize_path from mdio.api.io import to_mdio +from mdio.builder.schemas.chunk_grid import RegularChunkGrid +from mdio.builder.schemas.chunk_grid import RegularChunkShape +from mdio.builder.schemas.compressors import Blosc +from mdio.builder.schemas.compressors import BloscCname +from mdio.builder.schemas.dtype import ScalarType from mdio.builder.schemas.v1.units import LengthUnitEnum from mdio.builder.schemas.v1.units import LengthUnitModel +from mdio.builder.schemas.v1.variable import VariableMetadata from mdio.builder.xarray_builder import to_xarray_dataset from mdio.converters.exceptions import EnvironmentFormatError from mdio.converters.exceptions import GridTraceCountError from mdio.converters.exceptions import GridTraceSparsityError from mdio.converters.type_converter import to_structured_type from mdio.core.grid import Grid -from mdio.builder.schemas.chunk_grid import RegularChunkGrid -from mdio.builder.schemas.chunk_grid import RegularChunkShape -from mdio.builder.schemas.compressors import Blosc -from mdio.builder.schemas.compressors import BloscCname -from mdio.builder.schemas.dtype import ScalarType from mdio.segy import blocked_io from mdio.segy.utilities import get_grid_plan @@ -340,6 +341,7 @@ def _add_grid_override_to_metadata(dataset: Dataset, grid_overrides: dict[str, A def _add_raw_headers_to_template(mdio_template: AbstractDatasetTemplate) -> AbstractDatasetTemplate: """Add raw headers capability to the MDIO template by monkey-patching its _add_variables method. + This function modifies the template's _add_variables method to also add a raw headers variable with the following characteristics: - Same rank as the Headers variable (all dimensions except vertical) @@ -351,9 +353,11 @@ def _add_raw_headers_to_template(mdio_template: AbstractDatasetTemplate) -> Abst - Chunked the same as the Headers variable Args: mdio_template: The MDIO template to mutate + Returns: + The mutated MDIO template """ # Check if raw headers enhancement has already been applied to avoid duplicate additions - if hasattr(mdio_template, '_mdio_raw_headers_enhanced'): + if hasattr(mdio_template, "_mdio_raw_headers_enhanced"): return mdio_template # Store the original _add_variables method @@ -368,7 +372,6 @@ def enhanced_add_variables() -> None: # Create chunk grid metadata chunk_metadata = RegularChunkGrid(configuration=RegularChunkShape(chunk_shape=chunk_shape)) - from mdio.builder.schemas.v1.variable import VariableMetadata # Add the raw headers variable using the builder's add_variable method mdio_template._builder.add_variable( diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 3bf3d147..10e21383 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -155,39 +155,39 @@ def trace_worker( # noqa: PLR0913 ) if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - + # Get the indices where we need to place results live_mask = not_null live_positions = np.where(live_mask.ravel())[0] - + if len(live_positions) > 0: # Calculate byte ranges for headers - HEADER_SIZE = 240 + header_size = 240 trace_offset = segy_file.spec.trace.offset trace_itemsize = segy_file.spec.trace.itemsize - + starts = [] ends = [] for global_trace_idx in live_trace_indexes: header_start = trace_offset + global_trace_idx * trace_itemsize - header_end = header_start + HEADER_SIZE + header_end = header_start + header_size starts.append(header_start) ends.append(header_end) - + # Capture raw bytes raw_header_bytes = merge_cat_file(segy_file.fs, segy_file.url, starts, ends) - + # Convert and place results raw_headers_array = np.frombuffer(bytes(raw_header_bytes), dtype="|V240") tmp_raw_headers.ravel()[live_positions] = raw_headers_array - + ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, tmp_raw_headers, attrs=ds_to_write[raw_header_key].attrs, encoding=ds_to_write[raw_header_key].encoding, ) - + 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) From 7516ab7af965de0a9900759533f61036957ef6fa Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 10 Sep 2025 16:31:18 +0000 Subject: [PATCH 05/39] Add v2 issue check --- src/mdio/converters/segy.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index df4c259e..fe1fd5f0 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -11,6 +11,7 @@ from segy.config import SegySettings from segy.standards.codes import MeasurementSystem as segy_MeasurementSystem from segy.standards.fields.trace import Rev0 as TraceHeaderFieldsRev0 +import zarr from mdio.api.io import _normalize_path from mdio.api.io import to_mdio @@ -23,6 +24,7 @@ from mdio.builder.schemas.v1.units import LengthUnitModel from mdio.builder.schemas.v1.variable import VariableMetadata from mdio.builder.xarray_builder import to_xarray_dataset +from mdio.constants import ZarrFormat from mdio.converters.exceptions import EnvironmentFormatError from mdio.converters.exceptions import GridTraceCountError from mdio.converters.exceptions import GridTraceSparsityError @@ -434,8 +436,11 @@ def segy_to_mdio( # noqa PLR0913 header_dtype = to_structured_type(segy_spec.trace.header.dtype) if os.getenv("MDIO__DO_RAW_HEADERS") == "1": - logger.warning("MDIO__DO_RAW_HEADERS is experimental and expected to change or be removed.") - mdio_template = _add_raw_headers_to_template(mdio_template) + if zarr.config.get("default_zarr_format") == ZarrFormat.V2: + logger.warning("Raw headers are only supported for Zarr v3. Skipping raw headers.") + else: + logger.warning("MDIO__DO_RAW_HEADERS is experimental and expected to change or be removed.") + mdio_template = _add_raw_headers_to_template(mdio_template) horizontal_unit = _get_horizontal_coordinate_unit(segy_dimensions) mdio_ds: Dataset = mdio_template.build_dataset( From ed6116e008cc7bb0805ab3247b967095f6ba9e37 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 10 Sep 2025 18:48:51 +0000 Subject: [PATCH 06/39] Fix pre-commit --- src/mdio/converters/segy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index fe1fd5f0..a96761c3 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -7,11 +7,11 @@ from typing import TYPE_CHECKING import numpy as np +import zarr from segy import SegyFile from segy.config import SegySettings from segy.standards.codes import MeasurementSystem as segy_MeasurementSystem from segy.standards.fields.trace import Rev0 as TraceHeaderFieldsRev0 -import zarr from mdio.api.io import _normalize_path from mdio.api.io import to_mdio From 7dad280c43ee7553a93982aa47ee16c3ab2a8319 Mon Sep 17 00:00:00 2001 From: Brian Michell Date: Mon, 15 Sep 2025 13:24:29 -0700 Subject: [PATCH 07/39] Profiled disaster recovery array (#8) - Avoids duplicate read regression issue - Implements isolated and testable logic --- src/mdio/segy/_disaster_recovery_wrapper.py | 71 ++++ src/mdio/segy/_workers.py | 43 +- src/mdio/segy/blocked_io.py | 2 +- tests/unit/test_disaster_recovery_wrapper.py | 420 +++++++++++++++++++ 4 files changed, 505 insertions(+), 31 deletions(-) create mode 100644 src/mdio/segy/_disaster_recovery_wrapper.py create mode 100644 tests/unit/test_disaster_recovery_wrapper.py diff --git a/src/mdio/segy/_disaster_recovery_wrapper.py b/src/mdio/segy/_disaster_recovery_wrapper.py new file mode 100644 index 00000000..4b4ccca3 --- /dev/null +++ b/src/mdio/segy/_disaster_recovery_wrapper.py @@ -0,0 +1,71 @@ +"""Consumer-side utility to get both raw and transformed header data with single filesystem read.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import numpy as np + from segy.file import SegyFile + from segy.transforms import Transform, ByteSwapTransform, IbmFloatTransform + from numpy.typing import NDArray + +def _reverse_single_transform(data: NDArray, transform: Transform, endianness: Endianness) -> NDArray: + """Reverse a single transform operation.""" + from segy.schema import Endianness + from segy.transforms import ByteSwapTransform + from segy.transforms import IbmFloatTransform + + if isinstance(transform, ByteSwapTransform): + # Reverse the endianness conversion + if endianness == Endianness.LITTLE: + return data + + reverse_transform = ByteSwapTransform(Endianness.BIG) + return reverse_transform.apply(data) + + elif isinstance(transform, IbmFloatTransform): # TODO: This seems incorrect... + # Reverse IBM float conversion + reverse_direction = "to_ibm" if transform.direction == "to_ieee" else "to_ieee" + reverse_transform = IbmFloatTransform(reverse_direction, transform.keys) + return reverse_transform.apply(data) + + else: + # For unknown transforms, return data unchanged + return data + +def get_header_raw_and_transformed( + segy_file: SegyFile, + indices: int | list[int] | NDArray | slice, + do_reverse_transforms: bool = True +) -> tuple[NDArray | None, NDArray, NDArray]: + """Get both raw and transformed header data. + + Args: + segy_file: The SegyFile instance + indices: Which headers to retrieve + do_reverse_transforms: Whether to apply the reverse transform to get raw data + + Returns: + Tuple of (raw_headers, transformed_headers, traces) + """ + traces = segy_file.trace[indices] + transformed_headers = traces.header + + # Reverse transforms to get raw data + if do_reverse_transforms: + raw_headers = _reverse_transforms(transformed_headers, segy_file.header.transform_pipeline, segy_file.spec.endianness) + else: + raw_headers = None + + return raw_headers, transformed_headers, traces + +def _reverse_transforms(transformed_data: NDArray, transform_pipeline, endianness: Endianness) -> NDArray: + """Reverse the transform pipeline to get raw data.""" + raw_data = transformed_data.copy() if hasattr(transformed_data, 'copy') else transformed_data + + # Apply transforms in reverse order + for transform in reversed(transform_pipeline.transforms): + raw_data = _reverse_single_transform(raw_data, transform, endianness) + + return raw_data diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 10e21383..55b290a6 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -13,6 +13,7 @@ from mdio.api.io import to_mdio from mdio.builder.schemas.dtype import ScalarType +from mdio.segy._disaster_recovery_wrapper import get_header_raw_and_transformed if TYPE_CHECKING: from segy.arrays import HeaderArray @@ -81,7 +82,6 @@ def header_scan_worker( return cast("HeaderArray", trace_header) - def trace_worker( # noqa: PLR0913 segy_kw: SegyFileArguments, output_path: UPath, @@ -122,26 +122,31 @@ def trace_worker( # noqa: PLR0913 zarr_config.set({"threading.max_workers": 1}) live_trace_indexes = local_grid_map[not_null].tolist() - traces = segy_file.trace[live_trace_indexes] header_key = "headers" raw_header_key = "raw_headers" + # Used to disable the reverse transforms if we aren't going to write the raw headers + do_reverse_transforms = False + # Get subset of the dataset that has not yet been saved # The headers might not be present in the dataset 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) if raw_header_key in dataset.data_vars: + + do_reverse_transforms = True worker_variables.append(raw_header_key) + raw_headers, transformed_headers, traces = get_header_raw_and_transformed(segy_file, live_trace_indexes, do_reverse_transforms=do_reverse_transforms) ds_to_write = dataset[worker_variables] if header_key in worker_variables: # TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code # https://github.com/TGSAI/mdio-python/issues/584 tmp_headers = np.zeros_like(dataset[header_key]) - tmp_headers[not_null] = traces.header + tmp_headers[not_null] = transformed_headers # Create a new Variable object to avoid copying the temporary array # The ideal solution is to use `ds_to_write[header_key][:] = tmp_headers` # but Xarray appears to be copying memory instead of doing direct assignment. @@ -153,41 +158,19 @@ def trace_worker( # noqa: PLR0913 attrs=ds_to_write[header_key].attrs, encoding=ds_to_write[header_key].encoding, # Not strictly necessary, but safer than not doing it. ) + del transformed_headers # Manage memory if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - - # Get the indices where we need to place results - live_mask = not_null - live_positions = np.where(live_mask.ravel())[0] - - if len(live_positions) > 0: - # Calculate byte ranges for headers - header_size = 240 - trace_offset = segy_file.spec.trace.offset - trace_itemsize = segy_file.spec.trace.itemsize - - starts = [] - ends = [] - for global_trace_idx in live_trace_indexes: - header_start = trace_offset + global_trace_idx * trace_itemsize - header_end = header_start + header_size - starts.append(header_start) - ends.append(header_end) - - # Capture raw bytes - raw_header_bytes = merge_cat_file(segy_file.fs, segy_file.url, starts, ends) - - # Convert and place results - raw_headers_array = np.frombuffer(bytes(raw_header_bytes), dtype="|V240") - tmp_raw_headers.ravel()[live_positions] = raw_headers_array + tmp_raw_headers[not_null] = raw_headers.view("|V240") ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, tmp_raw_headers, attrs=ds_to_write[raw_header_key].attrs, - encoding=ds_to_write[raw_header_key].encoding, - ) + encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. + + del raw_headers # Manage memory 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) diff --git a/src/mdio/segy/blocked_io.py b/src/mdio/segy/blocked_io.py index 29d4b410..c0913dc7 100644 --- a/src/mdio/segy/blocked_io.py +++ b/src/mdio/segy/blocked_io.py @@ -280,4 +280,4 @@ def to_segy( non_consecutive_axes -= 1 - return block_io_records + return block_io_records \ No newline at end of file diff --git a/tests/unit/test_disaster_recovery_wrapper.py b/tests/unit/test_disaster_recovery_wrapper.py new file mode 100644 index 00000000..960d5479 --- /dev/null +++ b/tests/unit/test_disaster_recovery_wrapper.py @@ -0,0 +1,420 @@ +"""Test harness for disaster recovery wrapper. + +This module tests the _disaster_recovery_wrapper.py functionality by creating +test SEGY files with different configurations and validating that the raw headers +from get_header_raw_and_transformed match the bytes on disk. +""" + +from __future__ import annotations + +import tempfile +from pathlib import Path +from typing import TYPE_CHECKING + +import numpy as np +import pytest +from segy import SegyFile +from segy.factory import SegyFactory +from segy.schema import Endianness +from segy.schema import HeaderField +from segy.schema import SegySpec +from segy.standards import get_segy_standard + +from mdio.segy._disaster_recovery_wrapper import get_header_raw_and_transformed + +if TYPE_CHECKING: + from numpy.typing import NDArray + +SAMPLES_PER_TRACE = 1501 + +class TestDisasterRecoveryWrapper: + """Test cases for disaster recovery wrapper functionality.""" + + @pytest.fixture + def temp_dir(self) -> Path: + """Create a temporary directory for test files.""" + with tempfile.TemporaryDirectory() as tmp_dir: + yield Path(tmp_dir) + + @pytest.fixture + def basic_segy_spec(self) -> SegySpec: + """Create a basic SEGY specification for testing.""" + spec = get_segy_standard(1.0) + + # Add basic header fields for inline/crossline + header_fields = [ + HeaderField(name="inline", byte=189, format="int32"), + HeaderField(name="crossline", byte=193, format="int32"), + HeaderField(name="cdp_x", byte=181, format="int32"), + HeaderField(name="cdp_y", byte=185, format="int32"), + ] + + return spec.customize(trace_header_fields=header_fields) + + @pytest.fixture(params=[ + {"endianness": Endianness.BIG, "data_format": 1, "name": "big_endian_ibm"}, + {"endianness": Endianness.BIG, "data_format": 5, "name": "big_endian_ieee"}, + {"endianness": Endianness.LITTLE, "data_format": 1, "name": "little_endian_ibm"}, + {"endianness": Endianness.LITTLE, "data_format": 5, "name": "little_endian_ieee"}, + ]) + def segy_config(self, request) -> dict: + """Parameterized fixture for different SEGY configurations.""" + return request.param + + def create_test_segy_file( + self, + spec: SegySpec, + num_traces: int, + samples_per_trace: int, + output_path: Path, + endianness: Endianness = Endianness.BIG, + data_format: int = 1, # 1=IBM float, 5=IEEE float + inline_range: tuple[int, int] = (1, 5), + crossline_range: tuple[int, int] = (1, 5), + ) -> SegySpec: + """Create a test SEGY file with synthetic data.""" + # Update spec with desired endianness + spec.endianness = endianness + + factory = SegyFactory(spec=spec, samples_per_trace=samples_per_trace) + + # Create synthetic header data + headers = factory.create_trace_header_template(num_traces) + samples = factory.create_trace_sample_template(num_traces) + + # Set inline/crossline values + inline_start, inline_end = inline_range + crossline_start, crossline_end = crossline_range + + # Create a simple grid + inlines = np.arange(inline_start, inline_end + 1) + crosslines = np.arange(crossline_start, crossline_end + 1) + + trace_idx = 0 + for inline in inlines: + for crossline in crosslines: + if trace_idx >= num_traces: + break + + headers["inline"][trace_idx] = inline + headers["crossline"][trace_idx] = crossline + headers["cdp_x"][trace_idx] = inline * 100 # Simple coordinate calculation + headers["cdp_y"][trace_idx] = crossline * 100 + + # Create simple synthetic trace data + samples[trace_idx] = np.linspace(0, 1, samples_per_trace) + + trace_idx += 1 + + # Write the SEGY file with custom binary header + binary_header_updates = {"data_sample_format": data_format} + with output_path.open("wb") as f: + f.write(factory.create_textual_header()) + f.write(factory.create_binary_header(update=binary_header_updates)) + f.write(factory.create_traces(headers, samples)) + + return spec + + def extract_header_bytes_from_file( + self, segy_path: Path, trace_index: int, byte_start: int, byte_length: int + ) -> NDArray: + """Extract specific bytes from a trace header in the SEGY file.""" + with open(segy_path, "rb") as f: + # Skip text header (3200 bytes) + binary header (400 bytes) + header_offset = 3600 + + # Each trace: 240 byte header + samples + trace_size = 240 + SAMPLES_PER_TRACE * 4 # samples * 4 bytes each + trace_offset = header_offset + trace_index * trace_size + + f.seek(trace_offset + byte_start - 1) # SEGY is 1-based + header_bytes = f.read(byte_length) + + return np.frombuffer(header_bytes, dtype=np.uint8) + + def test_header_validation_configurations( + self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict + ) -> None: + """Test header validation with different SEGY configurations.""" + config_name = segy_config["name"] + endianness = segy_config["endianness"] + data_format = segy_config["data_format"] + + segy_path = temp_dir / f"test_{config_name}.segy" + + # Create test SEGY file + num_traces = 10 + samples_per_trace = SAMPLES_PER_TRACE + + spec = self.create_test_segy_file( + spec=basic_segy_spec, + num_traces=num_traces, + samples_per_trace=samples_per_trace, + output_path=segy_path, + endianness=endianness, + data_format=data_format, + ) + + # Load the SEGY file + segy_file = SegyFile(segy_path, spec=spec) + + # Test a few traces + test_indices = [0, 3, 7] + + for trace_idx in test_indices: + # Get raw and transformed headers + raw_headers, transformed_headers, traces = get_header_raw_and_transformed( + segy_file=segy_file, + indices=trace_idx, + do_reverse_transforms=True + ) + + # Extract bytes from disk for inline (bytes 189-192) and crossline (bytes 193-196) + inline_bytes_disk = self.extract_header_bytes_from_file( + segy_path, trace_idx, 189, 4 + ) + crossline_bytes_disk = self.extract_header_bytes_from_file( + segy_path, trace_idx, 193, 4 + ) + + # Convert raw headers to bytes for comparison + if raw_headers is not None: + # Extract from raw headers + # Note: We need to extract bytes directly from the structured array to preserve endianness + # Getting a scalar and calling .tobytes() loses endianness information + if raw_headers.ndim == 0: + # Single trace case + raw_data_bytes = raw_headers.tobytes() + inline_offset = raw_headers.dtype.fields['inline'][1] + crossline_offset = raw_headers.dtype.fields['crossline'][1] + inline_size = raw_headers.dtype.fields['inline'][0].itemsize + crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize + + raw_inline_bytes = np.frombuffer( + raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + ) + raw_crossline_bytes = np.frombuffer( + raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + ) + else: + # Multiple traces case - this test uses single trace index, so extract that trace + raw_data_bytes = raw_headers[0:1].tobytes() # Extract first trace + inline_offset = raw_headers.dtype.fields['inline'][1] + crossline_offset = raw_headers.dtype.fields['crossline'][1] + inline_size = raw_headers.dtype.fields['inline'][0].itemsize + crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize + + raw_inline_bytes = np.frombuffer( + raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + ) + raw_crossline_bytes = np.frombuffer( + raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + ) + + print(f"Transformed headers: {transformed_headers.tobytes()}") + print(f"Raw headers: {raw_headers.tobytes()}") + print(f"Inline bytes disk: {inline_bytes_disk.tobytes()}") + print(f"Crossline bytes disk: {crossline_bytes_disk.tobytes()}") + + # Compare bytes + assert np.array_equal(raw_inline_bytes, inline_bytes_disk), \ + f"Inline bytes mismatch for trace {trace_idx} in {config_name}" + assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), \ + f"Crossline bytes mismatch for trace {trace_idx} in {config_name}" + + def test_header_validation_no_transforms( + self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict + ) -> None: + """Test header validation when transforms are disabled.""" + config_name = segy_config["name"] + endianness = segy_config["endianness"] + data_format = segy_config["data_format"] + + segy_path = temp_dir / f"test_no_transforms_{config_name}.segy" + + # Create test SEGY file + num_traces = 5 + samples_per_trace = SAMPLES_PER_TRACE + + spec = self.create_test_segy_file( + spec=basic_segy_spec, + num_traces=num_traces, + samples_per_trace=samples_per_trace, + output_path=segy_path, + endianness=endianness, + data_format=data_format, + ) + + # Load the SEGY file + segy_file = SegyFile(segy_path, spec=spec) + + # Get headers without reverse transforms + raw_headers, transformed_headers, traces = get_header_raw_and_transformed( + segy_file=segy_file, + indices=slice(None), # All traces + do_reverse_transforms=False + ) + + # When transforms are disabled, raw_headers should be None + assert raw_headers is None + + # Transformed headers should still be available + assert transformed_headers is not None + assert transformed_headers.size == num_traces + + def test_multiple_traces_validation( + self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict + ) -> None: + """Test validation with multiple traces at once.""" + if True: + import segy + print(segy.__version__) + config_name = segy_config["name"] + endianness = segy_config["endianness"] + data_format = segy_config["data_format"] + + print(f"Config name: {config_name}") + print(f"Endianness: {endianness}") + print(f"Data format: {data_format}") + + segy_path = temp_dir / f"test_multiple_traces_{config_name}.segy" + + # Create test SEGY file with more traces + num_traces = 25 # 5x5 grid + samples_per_trace = SAMPLES_PER_TRACE + + spec = self.create_test_segy_file( + spec=basic_segy_spec, + num_traces=num_traces, + samples_per_trace=samples_per_trace, + output_path=segy_path, + endianness=endianness, + data_format=data_format, + inline_range=(1, 5), + crossline_range=(1, 5), + ) + + # Load the SEGY file + segy_file = SegyFile(segy_path, spec=spec) + + # Get all traces + raw_headers, transformed_headers, traces = get_header_raw_and_transformed( + segy_file=segy_file, + indices=slice(None), # All traces + do_reverse_transforms=True + ) + + first = True + + # Validate each trace + for trace_idx in range(num_traces): + # Extract bytes from disk + inline_bytes_disk = self.extract_header_bytes_from_file( + segy_path, trace_idx, 189, 4 + ) + crossline_bytes_disk = self.extract_header_bytes_from_file( + segy_path, trace_idx, 193, 4 + ) + + if first: + print(raw_headers.dtype) + print(raw_headers.shape) + first = False + + # Extract from raw headers + # Note: We need to extract bytes directly from the structured array to preserve endianness + # Getting a scalar and calling .tobytes() loses endianness information + if raw_headers.ndim == 0: + # Single trace case + raw_data_bytes = raw_headers.tobytes() + inline_offset = raw_headers.dtype.fields['inline'][1] + crossline_offset = raw_headers.dtype.fields['crossline'][1] + inline_size = raw_headers.dtype.fields['inline'][0].itemsize + crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize + + raw_inline_bytes = np.frombuffer( + raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + ) + raw_crossline_bytes = np.frombuffer( + raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + ) + else: + # Multiple traces case + raw_data_bytes = raw_headers[trace_idx:trace_idx+1].tobytes() + inline_offset = raw_headers.dtype.fields['inline'][1] + crossline_offset = raw_headers.dtype.fields['crossline'][1] + inline_size = raw_headers.dtype.fields['inline'][0].itemsize + crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize + + raw_inline_bytes = np.frombuffer( + raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + ) + raw_crossline_bytes = np.frombuffer( + raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + ) + + print(f"Raw inline bytes: {raw_inline_bytes.tobytes()}") + print(f"Inline bytes disk: {inline_bytes_disk.tobytes()}") + print(f"Raw crossline bytes: {raw_crossline_bytes.tobytes()}") + print(f"Crossline bytes disk: {crossline_bytes_disk.tobytes()}") + + # Compare + assert np.array_equal(raw_inline_bytes, inline_bytes_disk), \ + f"Inline bytes mismatch for trace {trace_idx} in {config_name}" + assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), \ + f"Crossline bytes mismatch for trace {trace_idx} in {config_name}" + + @pytest.mark.parametrize("trace_indices", [ + 0, # Single trace + [0, 2, 4], # Multiple specific traces + slice(1, 4), # Range of traces + ]) + def test_different_index_types( + self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict, trace_indices + ) -> None: + """Test with different types of trace indices.""" + config_name = segy_config["name"] + endianness = segy_config["endianness"] + data_format = segy_config["data_format"] + + segy_path = temp_dir / f"test_index_types_{config_name}.segy" + + # Create test SEGY file + num_traces = 10 + samples_per_trace = SAMPLES_PER_TRACE + + spec = self.create_test_segy_file( + spec=basic_segy_spec, + num_traces=num_traces, + samples_per_trace=samples_per_trace, + output_path=segy_path, + endianness=endianness, + data_format=data_format, + ) + + # Load the SEGY file + segy_file = SegyFile(segy_path, spec=spec) + + # Get headers with different index types + raw_headers, transformed_headers, traces = get_header_raw_and_transformed( + segy_file=segy_file, + indices=trace_indices, + do_reverse_transforms=True + ) + + # Basic validation that we got results + assert raw_headers is not None + assert transformed_headers is not None + assert traces is not None + + # Check that the number of results matches expectation + if isinstance(trace_indices, int): + expected_count = 1 + elif isinstance(trace_indices, list): + expected_count = len(trace_indices) + elif isinstance(trace_indices, slice): + expected_count = len(range(*trace_indices.indices(num_traces))) + else: + expected_count = 1 + + assert transformed_headers.size == expected_count From cc1895e7df8e6006ff198c4303271119d80fe793 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 15 Sep 2025 20:38:40 +0000 Subject: [PATCH 08/39] Fix unclosed parenthesis --- src/mdio/segy/_workers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 55b290a6..fd084648 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -168,6 +168,7 @@ def trace_worker( # noqa: PLR0913 tmp_raw_headers, attrs=ds_to_write[raw_header_key].attrs, encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. + ) del raw_headers # Manage memory From 8fa4928c3fc9da0e22bff84b6a15bd7c9a7e56de Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 15 Sep 2025 20:41:45 +0000 Subject: [PATCH 09/39] Linting --- src/mdio/segy/_disaster_recovery_wrapper.py | 39 ++--- src/mdio/segy/_workers.py | 8 +- src/mdio/segy/blocked_io.py | 2 +- tests/unit/test_disaster_recovery_wrapper.py | 141 +++++++++---------- 4 files changed, 94 insertions(+), 96 deletions(-) diff --git a/src/mdio/segy/_disaster_recovery_wrapper.py b/src/mdio/segy/_disaster_recovery_wrapper.py index 4b4ccca3..ad53a540 100644 --- a/src/mdio/segy/_disaster_recovery_wrapper.py +++ b/src/mdio/segy/_disaster_recovery_wrapper.py @@ -4,18 +4,19 @@ from typing import TYPE_CHECKING +from segy.schema import Endianness +from segy.transforms import ByteSwapTransform +from segy.transforms import IbmFloatTransform + if TYPE_CHECKING: - import numpy as np - from segy.file import SegyFile - from segy.transforms import Transform, ByteSwapTransform, IbmFloatTransform from numpy.typing import NDArray + from segy import SegyFile + from segy.transforms import Transform + from segy.transforms import TransformPipeline + def _reverse_single_transform(data: NDArray, transform: Transform, endianness: Endianness) -> NDArray: """Reverse a single transform operation.""" - from segy.schema import Endianness - from segy.transforms import ByteSwapTransform - from segy.transforms import IbmFloatTransform - if isinstance(transform, ByteSwapTransform): # Reverse the endianness conversion if endianness == Endianness.LITTLE: @@ -24,20 +25,19 @@ def _reverse_single_transform(data: NDArray, transform: Transform, endianness: E reverse_transform = ByteSwapTransform(Endianness.BIG) return reverse_transform.apply(data) - elif isinstance(transform, IbmFloatTransform): # TODO: This seems incorrect... + # TODO(BrianMichell): #0000 Do we actually need to worry about IBM/IEEE transforms here? + if isinstance(transform, IbmFloatTransform): # Reverse IBM float conversion reverse_direction = "to_ibm" if transform.direction == "to_ieee" else "to_ieee" reverse_transform = IbmFloatTransform(reverse_direction, transform.keys) return reverse_transform.apply(data) - else: - # For unknown transforms, return data unchanged - return data + # For unknown transforms, return data unchanged + return data + def get_header_raw_and_transformed( - segy_file: SegyFile, - indices: int | list[int] | NDArray | slice, - do_reverse_transforms: bool = True + segy_file: SegyFile, indices: int | list[int] | NDArray | slice, do_reverse_transforms: bool = True ) -> tuple[NDArray | None, NDArray, NDArray]: """Get both raw and transformed header data. @@ -54,15 +54,20 @@ def get_header_raw_and_transformed( # Reverse transforms to get raw data if do_reverse_transforms: - raw_headers = _reverse_transforms(transformed_headers, segy_file.header.transform_pipeline, segy_file.spec.endianness) + raw_headers = _reverse_transforms( + transformed_headers, segy_file.header.transform_pipeline, segy_file.spec.endianness + ) else: raw_headers = None return raw_headers, transformed_headers, traces -def _reverse_transforms(transformed_data: NDArray, transform_pipeline, endianness: Endianness) -> NDArray: + +def _reverse_transforms( + transformed_data: NDArray, transform_pipeline: TransformPipeline, endianness: Endianness +) -> NDArray: """Reverse the transform pipeline to get raw data.""" - raw_data = transformed_data.copy() if hasattr(transformed_data, 'copy') else transformed_data + raw_data = transformed_data.copy() if hasattr(transformed_data, "copy") else transformed_data # Apply transforms in reverse order for transform in reversed(transform_pipeline.transforms): diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index fd084648..56a46191 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -9,7 +9,6 @@ import numpy as np from segy import SegyFile -from segy.indexing import merge_cat_file from mdio.api.io import to_mdio from mdio.builder.schemas.dtype import ScalarType @@ -82,6 +81,7 @@ def header_scan_worker( return cast("HeaderArray", trace_header) + def trace_worker( # noqa: PLR0913 segy_kw: SegyFileArguments, output_path: UPath, @@ -135,11 +135,12 @@ def trace_worker( # noqa: PLR0913 if header_key in dataset.data_vars: # Keeping the `if` here to allow for more worker configurations worker_variables.append(header_key) if raw_header_key in dataset.data_vars: - do_reverse_transforms = True worker_variables.append(raw_header_key) - raw_headers, transformed_headers, traces = get_header_raw_and_transformed(segy_file, live_trace_indexes, do_reverse_transforms=do_reverse_transforms) + raw_headers, transformed_headers, traces = get_header_raw_and_transformed( + segy_file, live_trace_indexes, do_reverse_transforms=do_reverse_transforms + ) ds_to_write = dataset[worker_variables] if header_key in worker_variables: @@ -170,7 +171,6 @@ def trace_worker( # noqa: PLR0913 encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. ) - del raw_headers # Manage memory data_variable = ds_to_write[data_variable_name] fill_value = _get_fill_value(ScalarType(data_variable.dtype.name)) diff --git a/src/mdio/segy/blocked_io.py b/src/mdio/segy/blocked_io.py index c0913dc7..29d4b410 100644 --- a/src/mdio/segy/blocked_io.py +++ b/src/mdio/segy/blocked_io.py @@ -280,4 +280,4 @@ def to_segy( non_consecutive_axes -= 1 - return block_io_records \ No newline at end of file + return block_io_records diff --git a/tests/unit/test_disaster_recovery_wrapper.py b/tests/unit/test_disaster_recovery_wrapper.py index 960d5479..9ed5e045 100644 --- a/tests/unit/test_disaster_recovery_wrapper.py +++ b/tests/unit/test_disaster_recovery_wrapper.py @@ -27,6 +27,7 @@ SAMPLES_PER_TRACE = 1501 + class TestDisasterRecoveryWrapper: """Test cases for disaster recovery wrapper functionality.""" @@ -51,17 +52,19 @@ def basic_segy_spec(self) -> SegySpec: return spec.customize(trace_header_fields=header_fields) - @pytest.fixture(params=[ - {"endianness": Endianness.BIG, "data_format": 1, "name": "big_endian_ibm"}, - {"endianness": Endianness.BIG, "data_format": 5, "name": "big_endian_ieee"}, - {"endianness": Endianness.LITTLE, "data_format": 1, "name": "little_endian_ibm"}, - {"endianness": Endianness.LITTLE, "data_format": 5, "name": "little_endian_ieee"}, - ]) - def segy_config(self, request) -> dict: + @pytest.fixture( + params=[ + {"endianness": Endianness.BIG, "data_format": 1, "name": "big_endian_ibm"}, + {"endianness": Endianness.BIG, "data_format": 5, "name": "big_endian_ieee"}, + {"endianness": Endianness.LITTLE, "data_format": 1, "name": "little_endian_ibm"}, + {"endianness": Endianness.LITTLE, "data_format": 5, "name": "little_endian_ieee"}, + ] + ) + def segy_config(self, request: pytest.FixtureRequest) -> dict: """Parameterized fixture for different SEGY configurations.""" return request.param - def create_test_segy_file( + def create_test_segy_file( # noqa: PLR0913 self, spec: SegySpec, num_traces: int, @@ -119,7 +122,7 @@ def extract_header_bytes_from_file( self, segy_path: Path, trace_index: int, byte_start: int, byte_length: int ) -> NDArray: """Extract specific bytes from a trace header in the SEGY file.""" - with open(segy_path, "rb") as f: + with segy_path.open("rb") as f: # Skip text header (3200 bytes) + binary header (400 bytes) header_offset = 3600 @@ -164,18 +167,12 @@ def test_header_validation_configurations( for trace_idx in test_indices: # Get raw and transformed headers raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - segy_file=segy_file, - indices=trace_idx, - do_reverse_transforms=True + segy_file=segy_file, indices=trace_idx, do_reverse_transforms=True ) # Extract bytes from disk for inline (bytes 189-192) and crossline (bytes 193-196) - inline_bytes_disk = self.extract_header_bytes_from_file( - segy_path, trace_idx, 189, 4 - ) - crossline_bytes_disk = self.extract_header_bytes_from_file( - segy_path, trace_idx, 193, 4 - ) + inline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 189, 4) + crossline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 193, 4) # Convert raw headers to bytes for comparison if raw_headers is not None: @@ -185,30 +182,30 @@ def test_header_validation_configurations( if raw_headers.ndim == 0: # Single trace case raw_data_bytes = raw_headers.tobytes() - inline_offset = raw_headers.dtype.fields['inline'][1] - crossline_offset = raw_headers.dtype.fields['crossline'][1] - inline_size = raw_headers.dtype.fields['inline'][0].itemsize - crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize - + inline_offset = raw_headers.dtype.fields["inline"][1] + crossline_offset = raw_headers.dtype.fields["crossline"][1] + inline_size = raw_headers.dtype.fields["inline"][0].itemsize + crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize + raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 ) raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 ) else: # Multiple traces case - this test uses single trace index, so extract that trace raw_data_bytes = raw_headers[0:1].tobytes() # Extract first trace - inline_offset = raw_headers.dtype.fields['inline'][1] - crossline_offset = raw_headers.dtype.fields['crossline'][1] - inline_size = raw_headers.dtype.fields['inline'][0].itemsize - crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize - + inline_offset = raw_headers.dtype.fields["inline"][1] + crossline_offset = raw_headers.dtype.fields["crossline"][1] + inline_size = raw_headers.dtype.fields["inline"][0].itemsize + crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize + raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 ) raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 ) print(f"Transformed headers: {transformed_headers.tobytes()}") @@ -217,10 +214,12 @@ def test_header_validation_configurations( print(f"Crossline bytes disk: {crossline_bytes_disk.tobytes()}") # Compare bytes - assert np.array_equal(raw_inline_bytes, inline_bytes_disk), \ + assert np.array_equal(raw_inline_bytes, inline_bytes_disk), ( f"Inline bytes mismatch for trace {trace_idx} in {config_name}" - assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), \ + ) + assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), ( f"Crossline bytes mismatch for trace {trace_idx} in {config_name}" + ) def test_header_validation_no_transforms( self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict @@ -252,7 +251,7 @@ def test_header_validation_no_transforms( raw_headers, transformed_headers, traces = get_header_raw_and_transformed( segy_file=segy_file, indices=slice(None), # All traces - do_reverse_transforms=False + do_reverse_transforms=False, ) # When transforms are disabled, raw_headers should be None @@ -262,13 +261,8 @@ def test_header_validation_no_transforms( assert transformed_headers is not None assert transformed_headers.size == num_traces - def test_multiple_traces_validation( - self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict - ) -> None: + def test_multiple_traces_validation(self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict) -> None: """Test validation with multiple traces at once.""" - if True: - import segy - print(segy.__version__) config_name = segy_config["name"] endianness = segy_config["endianness"] data_format = segy_config["data_format"] @@ -301,7 +295,7 @@ def test_multiple_traces_validation( raw_headers, transformed_headers, traces = get_header_raw_and_transformed( segy_file=segy_file, indices=slice(None), # All traces - do_reverse_transforms=True + do_reverse_transforms=True, ) first = True @@ -309,12 +303,8 @@ def test_multiple_traces_validation( # Validate each trace for trace_idx in range(num_traces): # Extract bytes from disk - inline_bytes_disk = self.extract_header_bytes_from_file( - segy_path, trace_idx, 189, 4 - ) - crossline_bytes_disk = self.extract_header_bytes_from_file( - segy_path, trace_idx, 193, 4 - ) + inline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 189, 4) + crossline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 193, 4) if first: print(raw_headers.dtype) @@ -327,30 +317,30 @@ def test_multiple_traces_validation( if raw_headers.ndim == 0: # Single trace case raw_data_bytes = raw_headers.tobytes() - inline_offset = raw_headers.dtype.fields['inline'][1] - crossline_offset = raw_headers.dtype.fields['crossline'][1] - inline_size = raw_headers.dtype.fields['inline'][0].itemsize - crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize - + inline_offset = raw_headers.dtype.fields["inline"][1] + crossline_offset = raw_headers.dtype.fields["crossline"][1] + inline_size = raw_headers.dtype.fields["inline"][0].itemsize + crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize + raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 ) raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 ) else: # Multiple traces case - raw_data_bytes = raw_headers[trace_idx:trace_idx+1].tobytes() - inline_offset = raw_headers.dtype.fields['inline'][1] - crossline_offset = raw_headers.dtype.fields['crossline'][1] - inline_size = raw_headers.dtype.fields['inline'][0].itemsize - crossline_size = raw_headers.dtype.fields['crossline'][0].itemsize - + raw_data_bytes = raw_headers[trace_idx : trace_idx + 1].tobytes() + inline_offset = raw_headers.dtype.fields["inline"][1] + crossline_offset = raw_headers.dtype.fields["crossline"][1] + inline_size = raw_headers.dtype.fields["inline"][0].itemsize + crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize + raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset:inline_offset+inline_size], dtype=np.uint8 + raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 ) raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset:crossline_offset+crossline_size], dtype=np.uint8 + raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 ) print(f"Raw inline bytes: {raw_inline_bytes.tobytes()}") @@ -359,18 +349,23 @@ def test_multiple_traces_validation( print(f"Crossline bytes disk: {crossline_bytes_disk.tobytes()}") # Compare - assert np.array_equal(raw_inline_bytes, inline_bytes_disk), \ + assert np.array_equal(raw_inline_bytes, inline_bytes_disk), ( f"Inline bytes mismatch for trace {trace_idx} in {config_name}" - assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), \ + ) + assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), ( f"Crossline bytes mismatch for trace {trace_idx} in {config_name}" + ) - @pytest.mark.parametrize("trace_indices", [ - 0, # Single trace - [0, 2, 4], # Multiple specific traces - slice(1, 4), # Range of traces - ]) + @pytest.mark.parametrize( + "trace_indices", + [ + 0, # Single trace + [0, 2, 4], # Multiple specific traces + slice(1, 4), # Range of traces + ], + ) def test_different_index_types( - self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict, trace_indices + self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict, trace_indices: int | list[int] | slice ) -> None: """Test with different types of trace indices.""" config_name = segy_config["name"] @@ -397,9 +392,7 @@ def test_different_index_types( # Get headers with different index types raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - segy_file=segy_file, - indices=trace_indices, - do_reverse_transforms=True + segy_file=segy_file, indices=trace_indices, do_reverse_transforms=True ) # Basic validation that we got results From a5154c67745f17e3dd5ffff5a215430851211c1c Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 15 Sep 2025 21:20:19 +0000 Subject: [PATCH 10/39] Test DR compatibility with all tested schemas --- .../test_segy_import_export_masked.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 064ce1d7..dfc5b441 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -289,11 +289,32 @@ def export_masked_path(tmp_path_factory: pytest.TempPathFactory) -> Path: return tmp_path_factory.getbasetemp() / "export_masked" +@pytest.fixture +def raw_headers_env(request: pytest.FixtureRequest) -> None: + """Fixture to set/unset MDIO__DO_RAW_HEADERS environment variable.""" + env_value = request.param + if env_value is not None: + os.environ["MDIO__DO_RAW_HEADERS"] = env_value + else: + os.environ.pop("MDIO__DO_RAW_HEADERS", None) + + yield + + # Cleanup after test + os.environ.pop("MDIO__DO_RAW_HEADERS", None) + + @pytest.mark.parametrize( "test_conf", [STACK_2D_CONF, STACK_3D_CONF, GATHER_2D_CONF, GATHER_3D_CONF, STREAMER_2D_CONF, STREAMER_3D_CONF, COCA_3D_CONF], ids=["2d_stack", "3d_stack", "2d_gather", "3d_gather", "2d_streamer", "3d_streamer", "3d_coca"], ) +@pytest.mark.parametrize( + "raw_headers_env", + ["1", None], + ids=["with_raw_headers", "without_raw_headers"], + indirect=True, +) class TestNdImportExport: """Test import/export of n-D SEG-Ys to MDIO, with and without selection mask.""" From 280ea6258f1154a7bf0100113cd95464c57d9ee7 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 16 Sep 2025 13:10:46 +0000 Subject: [PATCH 11/39] Fix missing test fixture error --- tests/integration/test_segy_import_export_masked.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index dfc5b441..0b8b3841 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -318,7 +318,7 @@ def raw_headers_env(request: pytest.FixtureRequest) -> None: class TestNdImportExport: """Test import/export of n-D SEG-Ys to MDIO, with and without selection mask.""" - def test_import(self, test_conf: MaskedExportConfig, export_masked_path: Path) -> None: + def test_import(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: """Test import of an n-D SEG-Y file to MDIO. NOTE: This test must be executed before running 'test_ingested_mdio', 'test_export', and @@ -340,7 +340,7 @@ def test_import(self, test_conf: MaskedExportConfig, export_masked_path: Path) - overwrite=True, ) - def test_ingested_mdio(self, test_conf: MaskedExportConfig, export_masked_path: Path) -> None: + def test_ingested_mdio(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: """Verify if ingested data is correct. NOTE: This test must be executed after the 'test_import' successfully completes @@ -395,7 +395,7 @@ def test_ingested_mdio(self, test_conf: MaskedExportConfig, export_masked_path: expected = np.arange(1, num_traces + 1, dtype=np.float32).reshape(live_mask.shape) assert np.array_equal(actual, expected) - def test_export(self, test_conf: MaskedExportConfig, export_masked_path: Path) -> None: + def test_export(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: """Test export of an n-D MDIO file back to SEG-Y. NOTE: This test must be executed after the 'test_import' and 'test_ingested_mdio' @@ -432,7 +432,7 @@ def test_export(self, test_conf: MaskedExportConfig, export_masked_path: Path) - assert_array_equal(desired=expected_traces.header, actual=actual_traces.header) assert_array_equal(desired=expected_traces.sample, actual=actual_traces.sample) - def test_export_masked(self, test_conf: MaskedExportConfig, export_masked_path: Path) -> None: + def test_export_masked(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: """Test export of an n-D MDIO file back to SEG-Y with masked export. NOTE: This test must be executed after the 'test_import' and 'test_ingested_mdio' From c589bc06a792aa724e12f7fcf3b99d417af2d522 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 16 Sep 2025 14:27:38 +0000 Subject: [PATCH 12/39] Suppress unused linting error --- .../test_segy_import_export_masked.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 0b8b3841..2094ca4f 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -318,7 +318,7 @@ def raw_headers_env(request: pytest.FixtureRequest) -> None: class TestNdImportExport: """Test import/export of n-D SEG-Ys to MDIO, with and without selection mask.""" - def test_import(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: + def test_import(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: # noqa: ARG002 """Test import of an n-D SEG-Y file to MDIO. NOTE: This test must be executed before running 'test_ingested_mdio', 'test_export', and @@ -340,7 +340,12 @@ def test_import(self, test_conf: MaskedExportConfig, export_masked_path: Path, r overwrite=True, ) - def test_ingested_mdio(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: + def test_ingested_mdio( + self, + test_conf: MaskedExportConfig, + export_masked_path: Path, + raw_headers_env: None, # noqa: ARG002 + ) -> None: """Verify if ingested data is correct. NOTE: This test must be executed after the 'test_import' successfully completes @@ -395,7 +400,7 @@ def test_ingested_mdio(self, test_conf: MaskedExportConfig, export_masked_path: expected = np.arange(1, num_traces + 1, dtype=np.float32).reshape(live_mask.shape) assert np.array_equal(actual, expected) - def test_export(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: + def test_export(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: # noqa: ARG002 """Test export of an n-D MDIO file back to SEG-Y. NOTE: This test must be executed after the 'test_import' and 'test_ingested_mdio' @@ -432,7 +437,12 @@ def test_export(self, test_conf: MaskedExportConfig, export_masked_path: Path, r assert_array_equal(desired=expected_traces.header, actual=actual_traces.header) assert_array_equal(desired=expected_traces.sample, actual=actual_traces.sample) - def test_export_masked(self, test_conf: MaskedExportConfig, export_masked_path: Path, raw_headers_env: None) -> None: + def test_export_masked( + self, + test_conf: MaskedExportConfig, + export_masked_path: Path, + raw_headers_env: None, # noqa: ARG002 + ) -> None: """Test export of an n-D MDIO file back to SEG-Y with masked export. NOTE: This test must be executed after the 'test_import' and 'test_ingested_mdio' From c242eeb8f8d21aa4a2e5bf10c2073a5f6d8f60a9 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Fri, 19 Sep 2025 16:20:26 +0000 Subject: [PATCH 13/39] Attempt to use view --- src/mdio/segy/_workers.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 56a46191..3c16da44 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -138,16 +138,20 @@ def trace_worker( # noqa: PLR0913 do_reverse_transforms = True worker_variables.append(raw_header_key) - raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - segy_file, live_trace_indexes, do_reverse_transforms=do_reverse_transforms - ) + # raw_headers, transformed_headers, traces = get_header_raw_and_transformed( + # segy_file, live_trace_indexes, do_reverse_transforms=do_reverse_transforms + # ) + from copy import deepcopy # TODO: Move to head if we need to copy + header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) + traces = segy_file.trace[live_trace_indexes] ds_to_write = dataset[worker_variables] if header_key in worker_variables: # TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code # https://github.com/TGSAI/mdio-python/issues/584 tmp_headers = np.zeros_like(dataset[header_key]) - tmp_headers[not_null] = transformed_headers + # tmp_headers[not_null] = transformed_headers + tmp_headers[not_null] = header_pipeline.apply(traces.header.copy()) # Create a new Variable object to avoid copying the temporary array # The ideal solution is to use `ds_to_write[header_key][:] = tmp_headers` # but Xarray appears to be copying memory instead of doing direct assignment. @@ -159,10 +163,11 @@ def trace_worker( # noqa: PLR0913 attrs=ds_to_write[header_key].attrs, encoding=ds_to_write[header_key].encoding, # Not strictly necessary, but safer than not doing it. ) - del transformed_headers # Manage memory + # del transformed_headers # Manage memory if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - tmp_raw_headers[not_null] = raw_headers.view("|V240") + # tmp_raw_headers[not_null] = raw_headers.view("|V240") + tmp_raw_headers[not_null] = traces.header.view("|V240") ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, @@ -171,7 +176,7 @@ def trace_worker( # noqa: PLR0913 encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. ) - del raw_headers # Manage memory + # del raw_headers # Manage memory 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) From ddcadb6c83bd8cc046c2e205ec521f2793c1edcc Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Fri, 19 Sep 2025 16:25:03 +0000 Subject: [PATCH 14/39] Add hex-dump and MDIO output reproducer --- disaster_recovery_analysis/bootstrap.sh | 76 +++++++ .../ingest_both_teapots.py | 43 ++++ disaster_recovery_analysis/src/main.rs | 200 ++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100755 disaster_recovery_analysis/bootstrap.sh create mode 100644 disaster_recovery_analysis/ingest_both_teapots.py create mode 100644 disaster_recovery_analysis/src/main.rs diff --git a/disaster_recovery_analysis/bootstrap.sh b/disaster_recovery_analysis/bootstrap.sh new file mode 100755 index 00000000..a6557a2b --- /dev/null +++ b/disaster_recovery_analysis/bootstrap.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -e # Exit on any error + +echo "=== Zarr Hexdump Bootstrap Script ===" +echo + +# Check if Rust is installed, if not install it +if ! command -v rustc &> /dev/null; then + echo "Rust is not installed. Installing Rust..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source $HOME/.cargo/env + echo "✓ Rust installed successfully" +fi + +echo "✓ Rust is installed ($(rustc --version))" + +# Check if Cargo is available +if ! command -v cargo &> /dev/null; then + echo "Sourcing Rust environment..." + source $HOME/.cargo/env +fi + +echo "✓ Cargo is available ($(cargo --version))" +echo + +# Install blosc library if not present +echo "Checking for blosc library..." +if ! pkg-config --exists blosc; then + echo "Installing blosc library..." + sudo apt-get update + sudo apt-get install -y libblosc-dev pkg-config + echo "✓ Blosc library installed" +else + echo "✓ Blosc library already available" +fi +echo + +# Build the project +echo "Building the Zarr hexdump tool..." +if cargo build --release; then + echo "✓ Build successful!" +else + echo "✗ Build failed!" + exit 1 +fi + +echo +echo "=== Usage ===" +echo "To hexdump a Zarr array, run:" +echo " ./target/release/zarr-hexdump /path/to/your/zarr/array" +echo +echo "Or use cargo run:" +echo " cargo run --release -- /path/to/your/zarr/array" +echo + +# Check if a path was provided as an argument +if [ $# -eq 1 ]; then + ZARR_PATH="$1" + echo "=== Running hexdump on provided path: $ZARR_PATH ===" + echo + + if [ -e "$ZARR_PATH" ]; then + ./target/release/zarr-hexdump "$ZARR_PATH" + else + echo "Error: Path '$ZARR_PATH' does not exist!" + exit 1 + fi +elif [ $# -gt 1 ]; then + echo "Error: Too many arguments provided." + echo "Usage: $0 [zarr_array_path]" + exit 1 +else + echo "No Zarr array path provided. Build completed successfully." + echo "Use the commands above to run the hexdump tool." +fi diff --git a/disaster_recovery_analysis/ingest_both_teapots.py b/disaster_recovery_analysis/ingest_both_teapots.py new file mode 100644 index 00000000..04aa52f5 --- /dev/null +++ b/disaster_recovery_analysis/ingest_both_teapots.py @@ -0,0 +1,43 @@ +if __name__ == "__main__": + import mdio + from segy.standards import get_segy_standard + from segy.schema import HeaderField, Endianness + import os + from mdio.builder.template_registry import TemplateRegistry + + import logging + + logging.getLogger("segy").setLevel(logging.DEBUG) + + os.environ["MDIO__IMPORT__CLOUD_NATIVE"] = "true" + os.environ["MDIO__IMPORT__CPU_COUNT"] = "16" + os.environ["MDIO__DO_RAW_HEADERS"] = "1" + + custom_headers = [ + HeaderField(name="inline", byte=181, format="int32"), + HeaderField(name="crossline", byte=185, format="int32"), + HeaderField(name="cdp_x", byte=81, format="int32"), + HeaderField(name="cdp_y", byte=85, format="int32"), + ] + + big_endian_spec = get_segy_standard(0) + big_endian_spec.endianness = Endianness.BIG + little_endian_spec = get_segy_standard(0) + little_endian_spec.endianness = Endianness.LITTLE + big_endian_spec = big_endian_spec.customize(trace_header_fields=custom_headers) + little_endian_spec = little_endian_spec.customize(trace_header_fields=custom_headers) + + mdio.segy_to_mdio( + segy_spec=big_endian_spec, + mdio_template=TemplateRegistry().get("PostStack3DTime"), + input_path="filt_mig_IEEE_BigEndian_Rev1.sgy", + output_path="filt_mig_IEEE_BigEndian_Rev1.mdio", + overwrite=True, + ) + mdio.segy_to_mdio( + segy_spec=little_endian_spec, + mdio_template=TemplateRegistry().get("PostStack3DTime"), + input_path="filt_mig_IEEE_LittleEndian_Rev1.sgy", + output_path="filt_mig_IEEE_LittleEndian_Rev1.mdio", + overwrite=True, + ) \ No newline at end of file diff --git a/disaster_recovery_analysis/src/main.rs b/disaster_recovery_analysis/src/main.rs new file mode 100644 index 00000000..974e3808 --- /dev/null +++ b/disaster_recovery_analysis/src/main.rs @@ -0,0 +1,200 @@ +use std::env; +use std::fs; +use std::path::Path; +use walkdir::WalkDir; +fn decompress_blosc(compressed_data: &[u8]) -> Result, String> { + unsafe { + // Get decompressed size first + let mut nbytes = 0usize; + let mut cbytes = 0usize; + let mut blocksize = 0usize; + + blosc_sys::blosc_cbuffer_sizes( + compressed_data.as_ptr() as *const std::ffi::c_void, + &mut nbytes as *mut usize, + &mut cbytes as *mut usize, + &mut blocksize as *mut usize, + ); + + if nbytes == 0 { + return Err("Invalid compressed data".to_string()); + } + + // Allocate output buffer + let mut decompressed = vec![0u8; nbytes]; + + // Decompress + let result = blosc_sys::blosc_decompress( + compressed_data.as_ptr() as *const std::ffi::c_void, + decompressed.as_mut_ptr() as *mut std::ffi::c_void, + nbytes, + ); + + if result < 0 { + return Err(format!("Blosc decompression failed with code: {}", result)); + } + + decompressed.truncate(result as usize); + Ok(decompressed) + } +} + +fn print_hexdump(data: &[u8], offset: usize, chunk_name: &str) { + println!("=== {} ===", chunk_name); + for (i, chunk) in data.chunks(16).enumerate() { + let addr = offset + i * 16; + + // Print address + print!("{:08x} ", addr); + + // Print hex bytes + for (j, &byte) in chunk.iter().enumerate() { + if j == 8 { + print!(" "); // Extra space in the middle + } + print!("{:02x} ", byte); + } + + // Pad if chunk is less than 16 bytes + if chunk.len() < 16 { + for j in chunk.len()..16 { + if j == 8 { + print!(" "); + } + print!(" "); + } + } + + // Print ASCII representation + print!(" |"); + for &byte in chunk { + if byte >= 32 && byte <= 126 { + print!("{}", byte as char); + } else { + print!("."); + } + } + println!("|"); + } + println!(); +} + +fn main() -> Result<(), Box> { + let args: Vec = env::args().collect(); + + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + eprintln!("Example: {} /path/to/zarr/array", args[0]); + std::process::exit(1); + } + + let zarr_path = Path::new(&args[1]); + + // Verify the path exists + if !zarr_path.exists() { + eprintln!("Error: Path '{}' does not exist", zarr_path.display()); + std::process::exit(1); + } + + println!("Reading Zarr array from: {}", zarr_path.display()); + println!("========================================"); + + // Read zarr.json metadata + let zarr_json_path = zarr_path.join("zarr.json"); + if !zarr_json_path.exists() { + eprintln!("Error: zarr.json not found in {}", zarr_path.display()); + std::process::exit(1); + } + + let metadata_content = fs::read_to_string(&zarr_json_path)?; + let metadata: serde_json::Value = serde_json::from_str(&metadata_content)?; + + // Extract information from metadata + let shape = metadata["shape"].as_array().unwrap(); + let chunk_shape = metadata["chunk_grid"]["configuration"]["chunk_shape"].as_array().unwrap(); + + println!("Array shape: {:?}", shape); + println!("Chunk shape: {:?}", chunk_shape); + println!("Data type: {}", metadata["data_type"]["name"]); + if let Some(config) = metadata["data_type"]["configuration"].as_object() { + if let Some(length_bytes) = config.get("length_bytes") { + println!("Length bytes: {}", length_bytes); + } + } + println!(); + + // Calculate expected chunks based on the metadata we know: + // Shape: [345, 188], Chunk shape: [128, 128] + // This means we have ceil(345/128) = 3 chunks in dimension 0 + // and ceil(188/128) = 2 chunks in dimension 1 + // So we expect chunks: c/0/0, c/0/1, c/1/0, c/1/1, c/2/0, c/2/1 + + let mut chunk_files = Vec::new(); + + // Find all chunk files by walking the directory + for entry in WalkDir::new(zarr_path) { + let entry = entry?; + let path = entry.path(); + + // Look for chunk files (they start with 'c/' in Zarr v3) + if path.is_file() { + let relative_path = path.strip_prefix(zarr_path)?; + let path_str = relative_path.to_string_lossy(); + + if path_str.starts_with("c/") { + chunk_files.push((path.to_path_buf(), path_str.to_string())); + } + } + } + + // Sort chunk files for consistent ordering + chunk_files.sort_by(|a, b| a.1.cmp(&b.1)); + + println!("Found {} chunk files:", chunk_files.len()); + for (_, chunk_name) in &chunk_files { + println!(" {}", chunk_name); + } + println!(); + + let mut total_offset = 0; + + // Read, decompress, and hexdump each chunk file + for (chunk_path, chunk_name) in chunk_files { + match fs::read(&chunk_path) { + Ok(compressed_data) => { + if compressed_data.is_empty() { + println!("=== {} ===", chunk_name); + println!("(empty chunk)"); + println!(); + } else { + println!("Compressed size: {} bytes", compressed_data.len()); + + // Decompress the Blosc-compressed data using blosc-sys directly + match decompress_blosc(&compressed_data) { + Ok(decompressed_data) => { + println!("Decompressed size: {} bytes", decompressed_data.len()); + print_hexdump(&decompressed_data, total_offset, &chunk_name); + total_offset += decompressed_data.len(); + } + Err(e) => { + eprintln!("Error decompressing chunk {}: {}", chunk_name, e); + println!("Showing raw compressed data instead:"); + print_hexdump(&compressed_data, total_offset, &format!("{} (compressed)", chunk_name)); + total_offset += compressed_data.len(); + } + } + } + } + Err(e) => { + eprintln!("Error reading chunk {}: {}", chunk_name, e); + } + } + } + + println!("Total decompressed bytes processed: {}", total_offset); + println!(); + println!("Note: This shows the decompressed array data as it would appear in memory."); + println!("Each element is 240 bytes (raw_bytes with length_bytes: 240)."); + + Ok(()) +} From 3d48891870dc6ef838e42d52524ecd8432817016 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Fri, 19 Sep 2025 21:24:20 +0000 Subject: [PATCH 15/39] Fixes --- src/mdio/segy/_workers.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 3c16da44..f9b2c836 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -138,11 +138,9 @@ def trace_worker( # noqa: PLR0913 do_reverse_transforms = True worker_variables.append(raw_header_key) - # raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - # segy_file, live_trace_indexes, do_reverse_transforms=do_reverse_transforms - # ) from copy import deepcopy # TODO: Move to head if we need to copy header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) + segy_file.accessors.header_decode_pipeline.transforms = [] traces = segy_file.trace[live_trace_indexes] ds_to_write = dataset[worker_variables] @@ -166,8 +164,7 @@ def trace_worker( # noqa: PLR0913 # del transformed_headers # Manage memory if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - # tmp_raw_headers[not_null] = raw_headers.view("|V240") - tmp_raw_headers[not_null] = traces.header.view("|V240") + tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header).view("|V240") ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, From f1beb92f38b8c5bb01961d7b0c589b1c0d72c2c3 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Fri, 19 Sep 2025 21:24:55 +0000 Subject: [PATCH 16/39] Cleanup --- src/mdio/segy/_workers.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index f9b2c836..a19732e3 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -126,16 +126,12 @@ def trace_worker( # noqa: PLR0913 header_key = "headers" raw_header_key = "raw_headers" - # Used to disable the reverse transforms if we aren't going to write the raw headers - do_reverse_transforms = False - # Get subset of the dataset that has not yet been saved # The headers might not be present in the dataset 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) if raw_header_key in dataset.data_vars: - do_reverse_transforms = True worker_variables.append(raw_header_key) from copy import deepcopy # TODO: Move to head if we need to copy From d277d5b2bd157ca2b445656161c7a211e338e9ab Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 22 Sep 2025 14:00:19 +0000 Subject: [PATCH 17/39] Provide clean disaster recovery interface --- src/mdio/segy/_disaster_recovery_wrapper.py | 79 +++++---------------- src/mdio/segy/_workers.py | 18 ++--- 2 files changed, 28 insertions(+), 69 deletions(-) diff --git a/src/mdio/segy/_disaster_recovery_wrapper.py b/src/mdio/segy/_disaster_recovery_wrapper.py index ad53a540..3c2443c9 100644 --- a/src/mdio/segy/_disaster_recovery_wrapper.py +++ b/src/mdio/segy/_disaster_recovery_wrapper.py @@ -4,73 +4,32 @@ from typing import TYPE_CHECKING -from segy.schema import Endianness -from segy.transforms import ByteSwapTransform -from segy.transforms import IbmFloatTransform +from copy import deepcopy +import numpy as np if TYPE_CHECKING: from numpy.typing import NDArray from segy import SegyFile - from segy.transforms import Transform - from segy.transforms import TransformPipeline +class SegyFileTraceDataWrapper: -def _reverse_single_transform(data: NDArray, transform: Transform, endianness: Endianness) -> NDArray: - """Reverse a single transform operation.""" - if isinstance(transform, ByteSwapTransform): - # Reverse the endianness conversion - if endianness == Endianness.LITTLE: - return data + def __init__(self, segy_file: SegyFile, indices: int | list[int] | NDArray | slice): + self.segy_file = segy_file + self.indices = indices + self._header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) + segy_file.accessors.header_decode_pipeline.transforms = [] + self.traces = segy_file.trace[indices] - reverse_transform = ByteSwapTransform(Endianness.BIG) - return reverse_transform.apply(data) + @property + def header(self): + # The copy is necessary to avoid applying the pipeline to the original header. + return self._header_pipeline.apply(self.traces.header.copy()) - # TODO(BrianMichell): #0000 Do we actually need to worry about IBM/IEEE transforms here? - if isinstance(transform, IbmFloatTransform): - # Reverse IBM float conversion - reverse_direction = "to_ibm" if transform.direction == "to_ieee" else "to_ieee" - reverse_transform = IbmFloatTransform(reverse_direction, transform.keys) - return reverse_transform.apply(data) - # For unknown transforms, return data unchanged - return data + @property + def raw_header(self): + return np.ascontiguousarray(self.traces.header).view("|V240") - -def get_header_raw_and_transformed( - segy_file: SegyFile, indices: int | list[int] | NDArray | slice, do_reverse_transforms: bool = True -) -> tuple[NDArray | None, NDArray, NDArray]: - """Get both raw and transformed header data. - - Args: - segy_file: The SegyFile instance - indices: Which headers to retrieve - do_reverse_transforms: Whether to apply the reverse transform to get raw data - - Returns: - Tuple of (raw_headers, transformed_headers, traces) - """ - traces = segy_file.trace[indices] - transformed_headers = traces.header - - # Reverse transforms to get raw data - if do_reverse_transforms: - raw_headers = _reverse_transforms( - transformed_headers, segy_file.header.transform_pipeline, segy_file.spec.endianness - ) - else: - raw_headers = None - - return raw_headers, transformed_headers, traces - - -def _reverse_transforms( - transformed_data: NDArray, transform_pipeline: TransformPipeline, endianness: Endianness -) -> NDArray: - """Reverse the transform pipeline to get raw data.""" - raw_data = transformed_data.copy() if hasattr(transformed_data, "copy") else transformed_data - - # Apply transforms in reverse order - for transform in reversed(transform_pipeline.transforms): - raw_data = _reverse_single_transform(raw_data, transform, endianness) - - return raw_data + @property + def sample(self): + return self.traces.sample diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index a19732e3..cbf29a14 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -12,7 +12,7 @@ from mdio.api.io import to_mdio from mdio.builder.schemas.dtype import ScalarType -from mdio.segy._disaster_recovery_wrapper import get_header_raw_and_transformed +from mdio.segy._disaster_recovery_wrapper import SegyFileTraceDataWrapper if TYPE_CHECKING: from segy.arrays import HeaderArray @@ -134,18 +134,19 @@ def trace_worker( # noqa: PLR0913 if raw_header_key in dataset.data_vars: worker_variables.append(raw_header_key) - from copy import deepcopy # TODO: Move to head if we need to copy - header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) - segy_file.accessors.header_decode_pipeline.transforms = [] - traces = segy_file.trace[live_trace_indexes] + # traces = segy_file.trace[live_trace_indexes] + # Raw headers are not intended to remain as a feature of the SEGY ingestion. + # For that reason, we have wrapped the accessors to provide an interface that can be removed + # and not require additional changes to the below code. + # NOTE: The `raw_header_key` code block should be removed in full as it will become dead code. + traces = SegyFileTraceDataWrapper(segy_file, live_trace_indexes) ds_to_write = dataset[worker_variables] if header_key in worker_variables: # TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code # https://github.com/TGSAI/mdio-python/issues/584 tmp_headers = np.zeros_like(dataset[header_key]) - # tmp_headers[not_null] = transformed_headers - tmp_headers[not_null] = header_pipeline.apply(traces.header.copy()) + tmp_headers[not_null] = traces.header # Create a new Variable object to avoid copying the temporary array # The ideal solution is to use `ds_to_write[header_key][:] = tmp_headers` # but Xarray appears to be copying memory instead of doing direct assignment. @@ -157,10 +158,9 @@ def trace_worker( # noqa: PLR0913 attrs=ds_to_write[header_key].attrs, encoding=ds_to_write[header_key].encoding, # Not strictly necessary, but safer than not doing it. ) - # del transformed_headers # Manage memory if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header).view("|V240") + tmp_raw_headers[not_null] = traces.raw_header ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, From c9cf0e9d0f5fdc405c8b25c02c71199f21aaf4cb Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 22 Sep 2025 15:33:21 +0000 Subject: [PATCH 18/39] Begin work on tests --- .../test_segy_import_export_masked.py | 99 ++++++- tests/unit/test_disaster_recovery_wrapper.py | 267 +++++++----------- 2 files changed, 190 insertions(+), 176 deletions(-) diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 2094ca4f..2e208380 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -282,11 +282,16 @@ def generate_selection_mask(selection_conf: SelectionMaskConfig, grid_conf: Grid @pytest.fixture -def export_masked_path(tmp_path_factory: pytest.TempPathFactory) -> Path: +def export_masked_path(tmp_path_factory: pytest.TempPathFactory, raw_headers_env: None) -> Path: # noqa: ARG001 """Fixture that generates temp directory for export tests.""" + # Create path suffix based on current raw headers environment variable + # raw_headers_env dependency ensures the environment variable is set before this runs + raw_headers_enabled = os.getenv("MDIO__DO_RAW_HEADERS") == "1" + path_suffix = "with_raw_headers" if raw_headers_enabled else "without_raw_headers" + if DEBUG_MODE: - return Path("TMP/export_masked") - return tmp_path_factory.getbasetemp() / "export_masked" + return Path(f"TMP/export_masked_{path_suffix}") + return tmp_path_factory.getbasetemp() / f"export_masked_{path_suffix}" @pytest.fixture @@ -300,8 +305,33 @@ def raw_headers_env(request: pytest.FixtureRequest) -> None: yield - # Cleanup after test + # Cleanup after test - both environment variable and template state os.environ.pop("MDIO__DO_RAW_HEADERS", None) + + # Clean up any template modifications to ensure test isolation + from mdio.builder.template_registry import TemplateRegistry + registry = TemplateRegistry.get_instance() + + # Reset any templates that might have been modified with raw headers + template_names = ["PostStack2DTime", "PostStack3DTime", "PreStackCdpOffsetGathers2DTime", + "PreStackCdpOffsetGathers3DTime", "PreStackShotGathers2DTime", + "PreStackShotGathers3DTime", "PreStackCocaGathers3DTime"] + + for template_name in template_names: + try: + template = registry.get(template_name) + # Remove raw headers enhancement if present + if hasattr(template, "_mdio_raw_headers_enhanced"): + delattr(template, "_mdio_raw_headers_enhanced") + # The enhancement is applied by monkey-patching _add_variables + # We need to restore it to the original method from the class + # Since we can't easily restore the exact original, we'll get a fresh instance + template_class = type(template) + if hasattr(template_class, '_add_variables'): + template._add_variables = template_class._add_variables.__get__(template, template_class) + except KeyError: + # Template not found, skip + continue @pytest.mark.parametrize( @@ -471,3 +501,64 @@ def test_export_masked( # https://github.com/TGSAI/mdio-python/issues/610 assert_array_equal(actual_sgy.trace[:].header, expected_sgy.trace[expected_trc_idx].header) assert_array_equal(actual_sgy.trace[:].sample, expected_sgy.trace[expected_trc_idx].sample) + + def test_raw_headers_byte_preservation( + self, + test_conf: MaskedExportConfig, + export_masked_path: Path, + raw_headers_env: None, # noqa: ARG002 + ) -> None: + """Test that raw headers are preserved byte-for-byte when MDIO__DO_RAW_HEADERS=1.""" + grid_conf, segy_factory_conf, _, _ = test_conf + segy_path = export_masked_path / f"{grid_conf.name}.sgy" + mdio_path = export_masked_path / f"{grid_conf.name}.mdio" + + # Open MDIO dataset + ds = open_mdio(mdio_path) + + # Check if raw_headers should exist based on environment variable + has_raw_headers = "raw_headers" in ds.data_vars + if os.getenv("MDIO__DO_RAW_HEADERS") == "1": + assert has_raw_headers, "raw_headers should be present when MDIO__DO_RAW_HEADERS=1" + else: + assert not has_raw_headers, f"raw_headers should not be present when MDIO__DO_RAW_HEADERS is not set\n {ds}" + return # Exit early if raw_headers are not expected + + # Get data (only if raw_headers exist) + raw_headers_data = ds.raw_headers.values + trace_mask = ds.trace_mask.values + + # Verify 240-byte headers + assert raw_headers_data.dtype.itemsize == 240, f"Expected 240-byte headers, got {raw_headers_data.dtype.itemsize}" + + # Read raw bytes directly from SEG-Y file + def read_segy_trace_header(trace_index: int) -> bytes: + """Read 240-byte trace header directly from SEG-Y file.""" + with open(segy_path, 'rb') as f: + # Skip text (3200) + binary (400) headers = 3600 bytes + f.seek(3600) + # Each trace: 240 byte header + (num_samples * 4) byte samples + trace_size = 240 + (segy_factory_conf.num_samples * 4) + trace_offset = trace_index * trace_size + f.seek(trace_offset, 1) # Seek relative to current position + return f.read(240) + + # Compare all valid traces byte-by-byte + segy_trace_idx = 0 + flat_mask = trace_mask.ravel() + flat_raw_headers = raw_headers_data.reshape(-1, raw_headers_data.shape[-1]) + + for grid_idx in range(flat_mask.size): + if not flat_mask[grid_idx]: + continue + + # Get MDIO header as bytes + mdio_header_bytes = np.frombuffer(flat_raw_headers[grid_idx].tobytes(), dtype=np.uint8) + + # Get SEG-Y header as raw bytes directly from file + segy_raw_header_bytes = read_segy_trace_header(segy_trace_idx) + segy_header_bytes = np.frombuffer(segy_raw_header_bytes, dtype=np.uint8) + + # Compare byte-by-byte + assert_array_equal(mdio_header_bytes, segy_header_bytes) + segy_trace_idx += 1 diff --git a/tests/unit/test_disaster_recovery_wrapper.py b/tests/unit/test_disaster_recovery_wrapper.py index 9ed5e045..e4e3e31f 100644 --- a/tests/unit/test_disaster_recovery_wrapper.py +++ b/tests/unit/test_disaster_recovery_wrapper.py @@ -20,7 +20,7 @@ from segy.schema import SegySpec from segy.standards import get_segy_standard -from mdio.segy._disaster_recovery_wrapper import get_header_raw_and_transformed +from mdio.segy._disaster_recovery_wrapper import SegyFileTraceDataWrapper if TYPE_CHECKING: from numpy.typing import NDArray @@ -118,27 +118,11 @@ def create_test_segy_file( # noqa: PLR0913 return spec - def extract_header_bytes_from_file( - self, segy_path: Path, trace_index: int, byte_start: int, byte_length: int - ) -> NDArray: - """Extract specific bytes from a trace header in the SEGY file.""" - with segy_path.open("rb") as f: - # Skip text header (3200 bytes) + binary header (400 bytes) - header_offset = 3600 - # Each trace: 240 byte header + samples - trace_size = 240 + SAMPLES_PER_TRACE * 4 # samples * 4 bytes each - trace_offset = header_offset + trace_index * trace_size - - f.seek(trace_offset + byte_start - 1) # SEGY is 1-based - header_bytes = f.read(byte_length) - - return np.frombuffer(header_bytes, dtype=np.uint8) - - def test_header_validation_configurations( + def test_wrapper_basic_functionality( self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict ) -> None: - """Test header validation with different SEGY configurations.""" + """Test basic functionality of SegyFileTraceDataWrapper.""" config_name = segy_config["name"] endianness = segy_config["endianness"] data_format = segy_config["data_format"] @@ -161,75 +145,35 @@ def test_header_validation_configurations( # Load the SEGY file segy_file = SegyFile(segy_path, spec=spec) - # Test a few traces - test_indices = [0, 3, 7] - - for trace_idx in test_indices: - # Get raw and transformed headers - raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - segy_file=segy_file, indices=trace_idx, do_reverse_transforms=True - ) - - # Extract bytes from disk for inline (bytes 189-192) and crossline (bytes 193-196) - inline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 189, 4) - crossline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 193, 4) - - # Convert raw headers to bytes for comparison - if raw_headers is not None: - # Extract from raw headers - # Note: We need to extract bytes directly from the structured array to preserve endianness - # Getting a scalar and calling .tobytes() loses endianness information - if raw_headers.ndim == 0: - # Single trace case - raw_data_bytes = raw_headers.tobytes() - inline_offset = raw_headers.dtype.fields["inline"][1] - crossline_offset = raw_headers.dtype.fields["crossline"][1] - inline_size = raw_headers.dtype.fields["inline"][0].itemsize - crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize - - raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 - ) - raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 - ) - else: - # Multiple traces case - this test uses single trace index, so extract that trace - raw_data_bytes = raw_headers[0:1].tobytes() # Extract first trace - inline_offset = raw_headers.dtype.fields["inline"][1] - crossline_offset = raw_headers.dtype.fields["crossline"][1] - inline_size = raw_headers.dtype.fields["inline"][0].itemsize - crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize - - raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 - ) - raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 - ) - - print(f"Transformed headers: {transformed_headers.tobytes()}") - print(f"Raw headers: {raw_headers.tobytes()}") - print(f"Inline bytes disk: {inline_bytes_disk.tobytes()}") - print(f"Crossline bytes disk: {crossline_bytes_disk.tobytes()}") - - # Compare bytes - assert np.array_equal(raw_inline_bytes, inline_bytes_disk), ( - f"Inline bytes mismatch for trace {trace_idx} in {config_name}" - ) - assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), ( - f"Crossline bytes mismatch for trace {trace_idx} in {config_name}" - ) - - def test_header_validation_no_transforms( + # Test single trace + trace_idx = 3 + wrapper = SegyFileTraceDataWrapper(segy_file, trace_idx) + + # Test that properties are accessible + assert wrapper.header is not None + assert wrapper.raw_header is not None + assert wrapper.sample is not None + + # Test header properties + transformed_header = wrapper.header + raw_header = wrapper.raw_header + + # Raw header should be bytes (240 bytes per trace header) + assert raw_header.dtype == np.dtype("|V240") + + # Transformed header should have the expected fields + assert "inline" in transformed_header.dtype.names + assert "crossline" in transformed_header.dtype.names + + def test_wrapper_with_multiple_traces( self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict ) -> None: - """Test header validation when transforms are disabled.""" + """Test wrapper with multiple traces.""" config_name = segy_config["name"] endianness = segy_config["endianness"] data_format = segy_config["data_format"] - segy_path = temp_dir / f"test_no_transforms_{config_name}.segy" + segy_path = temp_dir / f"test_multiple_{config_name}.segy" # Create test SEGY file num_traces = 5 @@ -247,31 +191,26 @@ def test_header_validation_no_transforms( # Load the SEGY file segy_file = SegyFile(segy_path, spec=spec) - # Get headers without reverse transforms - raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - segy_file=segy_file, - indices=slice(None), # All traces - do_reverse_transforms=False, - ) + # Test with list of indices + trace_indices = [0, 2, 4] + wrapper = SegyFileTraceDataWrapper(segy_file, trace_indices) - # When transforms are disabled, raw_headers should be None - assert raw_headers is None + # Test that properties work with multiple traces + assert wrapper.header is not None + assert wrapper.raw_header is not None + assert wrapper.sample is not None - # Transformed headers should still be available - assert transformed_headers is not None - assert transformed_headers.size == num_traces + # Check that we got the expected number of traces + assert wrapper.header.size == len(trace_indices) + assert wrapper.raw_header.size == len(trace_indices) - def test_multiple_traces_validation(self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict) -> None: - """Test validation with multiple traces at once.""" + def test_wrapper_with_slice_indices(self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict) -> None: + """Test wrapper with slice indices.""" config_name = segy_config["name"] endianness = segy_config["endianness"] data_format = segy_config["data_format"] - print(f"Config name: {config_name}") - print(f"Endianness: {endianness}") - print(f"Data format: {data_format}") - - segy_path = temp_dir / f"test_multiple_traces_{config_name}.segy" + segy_path = temp_dir / f"test_slice_{config_name}.segy" # Create test SEGY file with more traces num_traces = 25 # 5x5 grid @@ -291,70 +230,18 @@ def test_multiple_traces_validation(self, temp_dir: Path, basic_segy_spec: SegyS # Load the SEGY file segy_file = SegyFile(segy_path, spec=spec) - # Get all traces - raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - segy_file=segy_file, - indices=slice(None), # All traces - do_reverse_transforms=True, - ) + # Test with slice + wrapper = SegyFileTraceDataWrapper(segy_file, slice(5, 15)) + + # Test that properties work with slice + assert wrapper.header is not None + assert wrapper.raw_header is not None + assert wrapper.sample is not None - first = True - - # Validate each trace - for trace_idx in range(num_traces): - # Extract bytes from disk - inline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 189, 4) - crossline_bytes_disk = self.extract_header_bytes_from_file(segy_path, trace_idx, 193, 4) - - if first: - print(raw_headers.dtype) - print(raw_headers.shape) - first = False - - # Extract from raw headers - # Note: We need to extract bytes directly from the structured array to preserve endianness - # Getting a scalar and calling .tobytes() loses endianness information - if raw_headers.ndim == 0: - # Single trace case - raw_data_bytes = raw_headers.tobytes() - inline_offset = raw_headers.dtype.fields["inline"][1] - crossline_offset = raw_headers.dtype.fields["crossline"][1] - inline_size = raw_headers.dtype.fields["inline"][0].itemsize - crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize - - raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 - ) - raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 - ) - else: - # Multiple traces case - raw_data_bytes = raw_headers[trace_idx : trace_idx + 1].tobytes() - inline_offset = raw_headers.dtype.fields["inline"][1] - crossline_offset = raw_headers.dtype.fields["crossline"][1] - inline_size = raw_headers.dtype.fields["inline"][0].itemsize - crossline_size = raw_headers.dtype.fields["crossline"][0].itemsize - - raw_inline_bytes = np.frombuffer( - raw_data_bytes[inline_offset : inline_offset + inline_size], dtype=np.uint8 - ) - raw_crossline_bytes = np.frombuffer( - raw_data_bytes[crossline_offset : crossline_offset + crossline_size], dtype=np.uint8 - ) - - print(f"Raw inline bytes: {raw_inline_bytes.tobytes()}") - print(f"Inline bytes disk: {inline_bytes_disk.tobytes()}") - print(f"Raw crossline bytes: {raw_crossline_bytes.tobytes()}") - print(f"Crossline bytes disk: {crossline_bytes_disk.tobytes()}") - - # Compare - assert np.array_equal(raw_inline_bytes, inline_bytes_disk), ( - f"Inline bytes mismatch for trace {trace_idx} in {config_name}" - ) - assert np.array_equal(raw_crossline_bytes, crossline_bytes_disk), ( - f"Crossline bytes mismatch for trace {trace_idx} in {config_name}" - ) + # Check that we got the expected number of traces (10 traces from slice(5, 15)) + expected_count = 10 + assert wrapper.header.size == expected_count + assert wrapper.raw_header.size == expected_count @pytest.mark.parametrize( "trace_indices", @@ -367,7 +254,7 @@ def test_multiple_traces_validation(self, temp_dir: Path, basic_segy_spec: SegyS def test_different_index_types( self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict, trace_indices: int | list[int] | slice ) -> None: - """Test with different types of trace indices.""" + """Test wrapper with different types of trace indices.""" config_name = segy_config["name"] endianness = segy_config["endianness"] data_format = segy_config["data_format"] @@ -390,15 +277,13 @@ def test_different_index_types( # Load the SEGY file segy_file = SegyFile(segy_path, spec=spec) - # Get headers with different index types - raw_headers, transformed_headers, traces = get_header_raw_and_transformed( - segy_file=segy_file, indices=trace_indices, do_reverse_transforms=True - ) + # Create wrapper with different index types + wrapper = SegyFileTraceDataWrapper(segy_file, trace_indices) # Basic validation that we got results - assert raw_headers is not None - assert transformed_headers is not None - assert traces is not None + assert wrapper.header is not None + assert wrapper.raw_header is not None + assert wrapper.sample is not None # Check that the number of results matches expectation if isinstance(trace_indices, int): @@ -410,4 +295,42 @@ def test_different_index_types( else: expected_count = 1 - assert transformed_headers.size == expected_count + assert wrapper.header.size == expected_count + + def test_header_pipeline_preservation( + self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict + ) -> None: + """Test that the wrapper preserves the original header pipeline.""" + config_name = segy_config["name"] + endianness = segy_config["endianness"] + data_format = segy_config["data_format"] + + segy_path = temp_dir / f"test_pipeline_{config_name}.segy" + + # Create test SEGY file + num_traces = 5 + samples_per_trace = SAMPLES_PER_TRACE + + spec = self.create_test_segy_file( + spec=basic_segy_spec, + num_traces=num_traces, + samples_per_trace=samples_per_trace, + output_path=segy_path, + endianness=endianness, + data_format=data_format, + ) + + # Load the SEGY file + segy_file = SegyFile(segy_path, spec=spec) + + # Store original pipeline transforms count + original_transforms_count = len(segy_file.accessors.header_decode_pipeline.transforms) + + # Create wrapper + wrapper = SegyFileTraceDataWrapper(segy_file, 0) + + # Verify that the original SEGY file's pipeline was modified (transforms cleared) + assert len(segy_file.accessors.header_decode_pipeline.transforms) == 0 + + # Verify that the wrapper has its own pipeline with the original transforms + assert len(wrapper._header_pipeline.transforms) == original_transforms_count From 9c8619a7f0e1e5a4e0069c372a9e85f45cc0d3d3 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 22 Sep 2025 15:45:52 +0000 Subject: [PATCH 19/39] Fix flattening issue --- tests/integration/test_segy_import_export_masked.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 2e208380..105eef59 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -546,14 +546,15 @@ def read_segy_trace_header(trace_index: int) -> bytes: # Compare all valid traces byte-by-byte segy_trace_idx = 0 flat_mask = trace_mask.ravel() - flat_raw_headers = raw_headers_data.reshape(-1, raw_headers_data.shape[-1]) + flat_raw_headers = raw_headers_data.ravel() # Flatten to 1D array of 240-byte header records for grid_idx in range(flat_mask.size): if not flat_mask[grid_idx]: continue - # Get MDIO header as bytes - mdio_header_bytes = np.frombuffer(flat_raw_headers[grid_idx].tobytes(), dtype=np.uint8) + # Get MDIO header as bytes - convert single header record to bytes + header_record = flat_raw_headers[grid_idx] + mdio_header_bytes = np.frombuffer(header_record.tobytes(), dtype=np.uint8) # Get SEG-Y header as raw bytes directly from file segy_raw_header_bytes = read_segy_trace_header(segy_trace_idx) From da493295d06a622ec5d3bdfd4d760947fb34b51f Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 22 Sep 2025 19:35:11 +0000 Subject: [PATCH 20/39] Push for debugging --- src/mdio/segy/_disaster_recovery_wrapper.py | 2 +- src/mdio/segy/_workers.py | 20 ++++++- tests/conftest.py | 2 +- .../test_segy_import_export_masked.py | 59 ++++++++++++++++++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/mdio/segy/_disaster_recovery_wrapper.py b/src/mdio/segy/_disaster_recovery_wrapper.py index 3c2443c9..8b4afb46 100644 --- a/src/mdio/segy/_disaster_recovery_wrapper.py +++ b/src/mdio/segy/_disaster_recovery_wrapper.py @@ -28,7 +28,7 @@ def header(self): @property def raw_header(self): - return np.ascontiguousarray(self.traces.header).view("|V240") + return np.ascontiguousarray(self.traces.header.copy()).view("|V240") @property def sample(self): diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index cbf29a14..c49d5bbb 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -139,14 +139,20 @@ def trace_worker( # noqa: PLR0913 # For that reason, we have wrapped the accessors to provide an interface that can be removed # and not require additional changes to the below code. # NOTE: The `raw_header_key` code block should be removed in full as it will become dead code. - traces = SegyFileTraceDataWrapper(segy_file, live_trace_indexes) + # traces = SegyFileTraceDataWrapper(segy_file, live_trace_indexes) + from copy import deepcopy + header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) + segy_file.accessors.header_decode_pipeline.transforms = [] + traces = segy_file.trace[live_trace_indexes] + ds_to_write = dataset[worker_variables] if header_key in worker_variables: # TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code # https://github.com/TGSAI/mdio-python/issues/584 tmp_headers = np.zeros_like(dataset[header_key]) - tmp_headers[not_null] = traces.header + # tmp_headers[not_null] = traces.header + tmp_headers[not_null] = header_pipeline.apply(traces.header.copy()) # Create a new Variable object to avoid copying the temporary array # The ideal solution is to use `ds_to_write[header_key][:] = tmp_headers` # but Xarray appears to be copying memory instead of doing direct assignment. @@ -160,7 +166,15 @@ def trace_worker( # noqa: PLR0913 ) if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - tmp_raw_headers[not_null] = traces.raw_header + # tmp_raw_headers = np.zeros((traces.header.shape[0], 240), dtype=np.) + # tmp_raw_headers[not_null] = traces.raw_header + np.set_printoptions(threshold=np.inf) + # print(traces.header.copy().view("|V240")) + # print(len(traces.header.view("|V240"))) + print(len(traces.header)) + tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") + # tmp_raw_headers[not_null] = traces.header.view("|V240") + # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, diff --git a/tests/conftest.py b/tests/conftest.py index 24bc06ef..bd2aa664 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ import pytest -DEBUG_MODE = False +DEBUG_MODE = True # Suppress Dask's chunk balancing warning warnings.filterwarnings( diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 105eef59..a80c5cd4 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -547,9 +547,13 @@ def read_segy_trace_header(trace_index: int) -> bytes: segy_trace_idx = 0 flat_mask = trace_mask.ravel() flat_raw_headers = raw_headers_data.ravel() # Flatten to 1D array of 240-byte header records + print(f"Flat mask shape: {flat_mask.shape}") + + operation = 'w' for grid_idx in range(flat_mask.size): if not flat_mask[grid_idx]: + print(f"Skipping trace {grid_idx} because it is masked") continue # Get MDIO header as bytes - convert single header record to bytes @@ -561,5 +565,58 @@ def read_segy_trace_header(trace_index: int) -> bytes: segy_header_bytes = np.frombuffer(segy_raw_header_bytes, dtype=np.uint8) # Compare byte-by-byte - assert_array_equal(mdio_header_bytes, segy_header_bytes) + # Write hexdumps to separate files for analysis + def hexdump_to_string(data: bytes, title: str) -> str: + """Create hexdump string.""" + lines = [f"{title}", "=" * len(title), ""] + + for i in range(0, len(data), 16): + # Address + addr = i + hex_part = "" + ascii_part = "" + + # Process 16 bytes at a time + for j in range(16): + if i + j < len(data): + byte_val = data[i + j] + hex_part += f"{byte_val:02x} " + ascii_part += chr(byte_val) if 32 <= byte_val <= 126 else "." + else: + hex_part += " " + ascii_part += " " + + lines.append(f"{addr:08x}: {hex_part} |{ascii_part}|") + + return "\n".join(lines) + + # Generate filenames for this test case + segy_filename = f"segy_headers_{grid_conf.name}.txt" + mdio_filename = f"mdio_headers_{grid_conf.name}.txt" + + # Append SEG-Y hexdump to file + with open(segy_filename, operation) as f: + if segy_trace_idx == 0: + f.write("") # Start fresh for first trace + else: + f.write("\n\n") # Add spacing between traces + f.write(hexdump_to_string(segy_header_bytes, + f"SEG-Y Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) + + # Append MDIO hexdump to file + with open(mdio_filename, operation) as f: + if segy_trace_idx == 0: + f.write("") # Start fresh for first trace + else: + f.write("\n\n") # Add spacing between traces + f.write(hexdump_to_string(mdio_header_bytes, + f"MDIO Raw Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) + operation = 'a' + + if segy_trace_idx == 0: + print(f"\nHeader hexdumps being written for {grid_conf.name}:") + print(f" SEG-Y: {segy_filename}") + print(f" MDIO: {mdio_filename}") + + segy_trace_idx += 1 From 7995912a66eeb7aaada58f91047eec968977c13f Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Mon, 22 Sep 2025 21:09:02 +0000 Subject: [PATCH 21/39] Numpy updates --- pyproject.toml | 1 + src/mdio/segy/_workers.py | 7 - tests/conftest.py | 2 +- .../test_segy_import_export_masked.py | 88 +++++---- uv.lock | 185 +++++++++--------- 5 files changed, 143 insertions(+), 140 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 479a6220..311da2b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dependencies = [ "click-params>=0.5.0", "dask>=2025.9.0", "fsspec>=2025.9.0", + "numpy>=2.3.3", "pint>=0.25.0", "psutil>=7.0.0", "pydantic>=2.11.9", diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index c49d5bbb..0fefa74e 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -166,15 +166,8 @@ def trace_worker( # noqa: PLR0913 ) if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - # tmp_raw_headers = np.zeros((traces.header.shape[0], 240), dtype=np.) # tmp_raw_headers[not_null] = traces.raw_header - np.set_printoptions(threshold=np.inf) - # print(traces.header.copy().view("|V240")) - # print(len(traces.header.view("|V240"))) - print(len(traces.header)) tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") - # tmp_raw_headers[not_null] = traces.header.view("|V240") - # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, diff --git a/tests/conftest.py b/tests/conftest.py index bd2aa664..24bc06ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ import pytest -DEBUG_MODE = True +DEBUG_MODE = False # Suppress Dask's chunk balancing warning warnings.filterwarnings( diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index a80c5cd4..b58c4068 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -563,60 +563,62 @@ def read_segy_trace_header(trace_index: int) -> bytes: # Get SEG-Y header as raw bytes directly from file segy_raw_header_bytes = read_segy_trace_header(segy_trace_idx) segy_header_bytes = np.frombuffer(segy_raw_header_bytes, dtype=np.uint8) + + assert_array_equal(mdio_header_bytes, segy_header_bytes) # Compare byte-by-byte # Write hexdumps to separate files for analysis - def hexdump_to_string(data: bytes, title: str) -> str: - """Create hexdump string.""" - lines = [f"{title}", "=" * len(title), ""] + # def hexdump_to_string(data: bytes, title: str) -> str: + # """Create hexdump string.""" + # lines = [f"{title}", "=" * len(title), ""] - for i in range(0, len(data), 16): - # Address - addr = i - hex_part = "" - ascii_part = "" + # for i in range(0, len(data), 16): + # # Address + # addr = i + # hex_part = "" + # ascii_part = "" - # Process 16 bytes at a time - for j in range(16): - if i + j < len(data): - byte_val = data[i + j] - hex_part += f"{byte_val:02x} " - ascii_part += chr(byte_val) if 32 <= byte_val <= 126 else "." - else: - hex_part += " " - ascii_part += " " + # # Process 16 bytes at a time + # for j in range(16): + # if i + j < len(data): + # byte_val = data[i + j] + # hex_part += f"{byte_val:02x} " + # ascii_part += chr(byte_val) if 32 <= byte_val <= 126 else "." + # else: + # hex_part += " " + # ascii_part += " " - lines.append(f"{addr:08x}: {hex_part} |{ascii_part}|") + # lines.append(f"{addr:08x}: {hex_part} |{ascii_part}|") - return "\n".join(lines) + # return "\n".join(lines) - # Generate filenames for this test case - segy_filename = f"segy_headers_{grid_conf.name}.txt" - mdio_filename = f"mdio_headers_{grid_conf.name}.txt" + # # Generate filenames for this test case + # segy_filename = f"segy_headers_{grid_conf.name}.txt" + # mdio_filename = f"mdio_headers_{grid_conf.name}.txt" - # Append SEG-Y hexdump to file - with open(segy_filename, operation) as f: - if segy_trace_idx == 0: - f.write("") # Start fresh for first trace - else: - f.write("\n\n") # Add spacing between traces - f.write(hexdump_to_string(segy_header_bytes, - f"SEG-Y Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) + # # Append SEG-Y hexdump to file + # with open(segy_filename, operation) as f: + # if segy_trace_idx == 0: + # f.write("") # Start fresh for first trace + # else: + # f.write("\n\n") # Add spacing between traces + # f.write(hexdump_to_string(segy_header_bytes, + # f"SEG-Y Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) - # Append MDIO hexdump to file - with open(mdio_filename, operation) as f: - if segy_trace_idx == 0: - f.write("") # Start fresh for first trace - else: - f.write("\n\n") # Add spacing between traces - f.write(hexdump_to_string(mdio_header_bytes, - f"MDIO Raw Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) - operation = 'a' + # # Append MDIO hexdump to file + # with open(mdio_filename, operation) as f: + # if segy_trace_idx == 0: + # f.write("") # Start fresh for first trace + # else: + # f.write("\n\n") # Add spacing between traces + # f.write(hexdump_to_string(mdio_header_bytes, + # f"MDIO Raw Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) + # operation = 'a' - if segy_trace_idx == 0: - print(f"\nHeader hexdumps being written for {grid_conf.name}:") - print(f" SEG-Y: {segy_filename}") - print(f" MDIO: {mdio_filename}") + # if segy_trace_idx == 0: + # print(f"\nHeader hexdumps being written for {grid_conf.name}:") + # print(f" SEG-Y: {segy_filename}") + # print(f" MDIO: {mdio_filename}") segy_trace_idx += 1 diff --git a/uv.lock b/uv.lock index dc0f6c03..d654c465 100644 --- a/uv.lock +++ b/uv.lock @@ -573,55 +573,63 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, - { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, - { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, - { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" }, - { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" }, - { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, - { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" }, - { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, - { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, - { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, - { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, - { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, - { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, - { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" }, - { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" }, - { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, - { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, - { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, - { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, - { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, - { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, - { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" }, - { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, ] [package.optional-dependencies] @@ -1855,6 +1863,7 @@ dependencies = [ { name = "click-params" }, { name = "dask" }, { name = "fsspec" }, + { name = "numpy" }, { name = "pint" }, { name = "psutil" }, { name = "pydantic" }, @@ -1917,6 +1926,7 @@ requires-dist = [ { name = "distributed", marker = "extra == 'distributed'", specifier = ">=2025.9.0" }, { name = "fsspec", specifier = ">=2025.9.0" }, { name = "gcsfs", marker = "extra == 'cloud'", specifier = ">=2025.9.0" }, + { name = "numpy", specifier = ">=2.3.3" }, { name = "pint", specifier = ">=0.25.0" }, { name = "psutil", specifier = ">=7.0.0" }, { name = "pydantic", specifier = ">=2.11.9" }, @@ -2731,11 +2741,11 @@ crypto = [ [[package]] name = "pyparsing" -version = "3.2.4" +version = "3.2.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/c9/b4594e6a81371dfa9eb7a2c110ad682acf985d96115ae8b25a1d63b4bf3b/pyparsing-3.2.4.tar.gz", hash = "sha256:fff89494f45559d0f2ce46613b419f632bbb6afbdaed49696d322bcf98a58e99", size = 1098809, upload-time = "2025-09-13T05:47:19.732Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", hash = "sha256:91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", size = 113869, upload-time = "2025-09-13T05:47:17.863Z" }, + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, ] [[package]] @@ -3123,37 +3133,34 @@ wheels = [ [[package]] name = "ruamel-yaml-clib" -version = "0.2.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, - { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, - { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, - { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, - { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, - { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" }, - { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" }, - { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" }, - { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" }, - { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" }, - { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011, upload-time = "2024-10-20T10:13:04.377Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488, upload-time = "2024-10-20T10:13:05.906Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066, upload-time = "2024-10-20T10:13:07.26Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785, upload-time = "2024-10-20T10:13:08.504Z" }, - { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017, upload-time = "2024-10-21T11:26:48.866Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270, upload-time = "2024-10-21T11:26:50.213Z" }, - { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059, upload-time = "2024-12-11T19:58:18.846Z" }, - { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583, upload-time = "2024-10-20T10:13:09.658Z" }, - { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190, upload-time = "2024-10-20T10:13:10.66Z" }, +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/e9/39ec4d4b3f91188fad1842748f67d4e749c77c37e353c4e545052ee8e893/ruamel.yaml.clib-0.2.14.tar.gz", hash = "sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e", size = 225394, upload-time = "2025-09-22T19:51:23.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/16/cb02815bc2ae9c66760c0c061d23c7358f9ba51dae95ac85247662b7fbe2/ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d", size = 137780, upload-time = "2025-09-22T19:50:37.734Z" }, + { url = "https://files.pythonhosted.org/packages/31/c6/fc687cd1b93bff8e40861eea46d6dc1a6a778d9a085684e4045ff26a8e40/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9", size = 641590, upload-time = "2025-09-22T19:50:41.978Z" }, + { url = "https://files.pythonhosted.org/packages/45/5d/65a2bc08b709b08576b3f307bf63951ee68a8e047cbbda6f1c9864ecf9a7/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70", size = 738090, upload-time = "2025-09-22T19:50:39.152Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d0/a70a03614d9a6788a3661ab1538879ed2aae4e84d861f101243116308a37/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98", size = 700744, upload-time = "2025-09-22T19:50:40.811Z" }, + { url = "https://files.pythonhosted.org/packages/40/85/e2c54ad637117cd13244a4649946eaa00f32edcb882d1f92df90e079ab00/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d", size = 743805, upload-time = "2025-09-22T19:50:43.58Z" }, + { url = "https://files.pythonhosted.org/packages/81/50/f899072c38877d8ef5382e0b3d47f8c4346226c1f52d6945d6f64fec6a2f/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c", size = 769529, upload-time = "2025-09-22T19:50:45.707Z" }, + { url = "https://files.pythonhosted.org/packages/99/7c/96d4b5075e30c65ea2064e40c2d657c7c235d7b6ef18751cf89a935b9041/ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl", hash = "sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a", size = 100256, upload-time = "2025-09-22T19:50:48.26Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8c/73ee2babd04e8bfcf1fd5c20aa553d18bf0ebc24b592b4f831d12ae46cc0/ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1", size = 118234, upload-time = "2025-09-22T19:50:47.019Z" }, + { url = "https://files.pythonhosted.org/packages/82/73/e628a92e80197ff6a79ab81ec3fa00d4cc082d58ab78d3337b7ba7043301/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052", size = 138842, upload-time = "2025-09-22T19:50:49.156Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c5/346c7094344a60419764b4b1334d9e0285031c961176ff88ffb652405b0c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a", size = 647404, upload-time = "2025-09-22T19:50:52.921Z" }, + { url = "https://files.pythonhosted.org/packages/df/99/65080c863eb06d4498de3d6c86f3e90595e02e159fd8529f1565f56cfe2c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29", size = 753141, upload-time = "2025-09-22T19:50:50.294Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e3/0de85f3e3333f8e29e4b10244374a202a87665d1131798946ee22cf05c7c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4", size = 703477, upload-time = "2025-09-22T19:50:51.508Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/959f10c2e2153cbdab834c46e6954b6dd9e3b109c8f8c0a3cf1618310985/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259", size = 745859, upload-time = "2025-09-22T19:50:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6b/e580a7c18b485e1a5f30a32cda96b20364b0ba649d9d2baaf72f8bd21f83/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023", size = 770200, upload-time = "2025-09-22T19:50:55.718Z" }, + { url = "https://files.pythonhosted.org/packages/ef/44/3455eebc761dc8e8fdced90f2b0a3fa61e32ba38b50de4130e2d57db0f21/ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl", hash = "sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54", size = 98829, upload-time = "2025-09-22T19:50:58.895Z" }, + { url = "https://files.pythonhosted.org/packages/76/ab/5121f7f3b651db93de546f8c982c241397aad0a4765d793aca1dac5eadee/ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68", size = 115570, upload-time = "2025-09-22T19:50:57.981Z" }, + { url = "https://files.pythonhosted.org/packages/72/06/7d51f4688d6d72bb72fa74254e1593c4f5ebd0036be5b41fe39315b275e9/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85", size = 137417, upload-time = "2025-09-22T19:50:59.82Z" }, + { url = "https://files.pythonhosted.org/packages/5a/08/b4499234a420ef42960eeb05585df5cc7eb25ccb8c980490b079e6367050/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e", size = 642558, upload-time = "2025-09-22T19:51:03.388Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ba/1975a27dedf1c4c33306ee67c948121be8710b19387aada29e2f139c43ee/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb", size = 744087, upload-time = "2025-09-22T19:51:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/20/15/8a19a13d27f3bd09fa18813add8380a29115a47b553845f08802959acbce/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d", size = 699709, upload-time = "2025-09-22T19:51:02.075Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/426b714abdc222392e68f3b8ad323930d05a214a27c7e7a0f06c69126401/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca", size = 740202, upload-time = "2025-09-22T19:51:04.673Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ac/3c5c2b27a183f4fda8a57c82211721c016bcb689a4a175865f7646db9f94/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6", size = 765196, upload-time = "2025-09-22T19:51:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/06f56a71fd55021c993ed6e848c9b2e5e9cfce180a42179f0ddd28253f7c/ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl", hash = "sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2", size = 98635, upload-time = "2025-09-22T19:51:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/51/79/76aba16a1689b50528224b182f71097ece338e7a4ab55e84c2e73443b78a/ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78", size = 115238, upload-time = "2025-09-22T19:51:07.081Z" }, ] [[package]] @@ -3686,15 +3693,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.35.0" +version = "0.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/5e/f0cd46063a02fd8515f0e880c37d2657845b7306c16ce6c4ffc44afd9036/uvicorn-0.36.0.tar.gz", hash = "sha256:527dc68d77819919d90a6b267be55f0e76704dca829d34aea9480be831a9b9d9", size = 80032, upload-time = "2025-09-20T01:07:14.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/96/06/5cc0542b47c0338c1cb676b348e24a1c29acabc81000bced518231dded6f/uvicorn-0.36.0-py3-none-any.whl", hash = "sha256:6bb4ba67f16024883af8adf13aba3a9919e415358604ce46780d3f9bdc36d731", size = 67675, upload-time = "2025-09-20T01:07:12.984Z" }, ] [[package]] @@ -3786,11 +3793,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] [[package]] From 78f1db9d4a23afd2216bb80325714af5b321d5c7 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 23 Sep 2025 21:35:22 +0000 Subject: [PATCH 22/39] Testing --- src/mdio/segy/_workers.py | 79 +++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 0fefa74e..8d4af5f4 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -147,6 +147,74 @@ def trace_worker( # noqa: PLR0913 ds_to_write = dataset[worker_variables] + if raw_header_key in worker_variables: + # from segy.schema import HeaderField, ScalarType + from segy.schema import HeaderField + from segy.schema import ScalarType as ScalarType2 + + new_spec = deepcopy(segy_file.spec) + new_spec.trace.header.fields = [] + new_fields = [] + + for i in range(240): + new_fields.append(HeaderField(name=f"Field_{i}", format=ScalarType.UINT8, byte=i+1)) + + # # new_spec.trace.header.fields = new_fields + new_spec = new_spec.customize(trace_header_fields=new_fields) + updated_segy_file = SegyFile(segy_file.url, spec=new_spec) + updated_traces = updated_segy_file.trace[live_trace_indexes] + + tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) + + + # _foo = traces.tobytes()[:240] + + # _asContiguousCopy = np.ascontiguousarray(traces.header.copy()).view("|V240") + # _asContiguousCopy1 = _asContiguousCopy[0] + + # _asContiguousNoCopy = np.ascontiguousarray(traces.header).view("|V240") + # _asContiguousNoCopy1 = _asContiguousNoCopy[0] + + # _asArrayCopy = traces.header.copy().view("|V240") + # _asArrayCopy1 = _asArrayCopy[0] + + # _asArrayNoCopy = traces.header.view("|V240") + # _asArrayNoCopy1 = _asArrayNoCopy[0] + + # _fo = type(traces.header) + + # _aahhhhhhh = segy_file.header[0] + + + + # _asBytes = traces.header.tobytes() + # _are_equal = _foo == _asBytes[:240] + # _asBytesLen = len(_asBytes) + # _asContiguousBytes = np.ascontiguousarray(traces.header).tobytes() + # _asContiguousBytesLen = len(_asContiguousBytes) + # _asContiguousBytesView = np.ascontiguousarray(traces.header[0]).tobytes().view("|V240") + # _asBuffer = memoryview(traces.header) + + + # _type = traces.header.dtype + + # tmp_raw_headers[not_null] = traces.raw_header + # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") + # tmp_raw_headers[not_null] = _asContiguousBytes + # tmp_raw_headers[not_null] = _asBytes + # tmp_raw_headers[not_null] = traces.header.view("|V240") + # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") # Leaks numpy metadata + + + tmp_raw_headers[not_null] = updated_traces.header.view("|V240") + + ds_to_write[raw_header_key] = Variable( + ds_to_write[raw_header_key].dims, + tmp_raw_headers, + attrs=ds_to_write[raw_header_key].attrs, + encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. + ) + if header_key in worker_variables: # TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code # https://github.com/TGSAI/mdio-python/issues/584 @@ -164,17 +232,6 @@ def trace_worker( # noqa: PLR0913 attrs=ds_to_write[header_key].attrs, encoding=ds_to_write[header_key].encoding, # Not strictly necessary, but safer than not doing it. ) - if raw_header_key in worker_variables: - tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - # tmp_raw_headers[not_null] = traces.raw_header - tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") - - ds_to_write[raw_header_key] = Variable( - ds_to_write[raw_header_key].dims, - tmp_raw_headers, - attrs=ds_to_write[raw_header_key].attrs, - encoding=ds_to_write[raw_header_key].encoding, # Not strictly necessary, but safer than not doing it. - ) # del raw_headers # Manage memory data_variable = ds_to_write[data_variable_name] From 6a638bcc729297bb83f45eb09e9b438946c4c463 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 15:00:19 +0000 Subject: [PATCH 23/39] Working end-to-end examples --- src/mdio/converters/segy.py | 44 +++++++++++++++++++ src/mdio/segy/_workers.py | 25 ++--------- .../test_segy_import_export_masked.py | 1 - 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index a96761c3..3d99fa61 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -12,6 +12,8 @@ from segy.config import SegySettings from segy.standards.codes import MeasurementSystem as segy_MeasurementSystem from segy.standards.fields.trace import Rev0 as TraceHeaderFieldsRev0 +from segy.schema import HeaderField +from segy.schema import ScalarType as ScalarType2 from mdio.api.io import _normalize_path from mdio.api.io import to_mdio @@ -340,6 +342,45 @@ def _add_grid_override_to_metadata(dataset: Dataset, grid_overrides: dict[str, A if grid_overrides is not None: dataset.metadata.attributes["gridOverrides"] = grid_overrides +def _scalar_to_size(scalar: ScalarType2) -> int: + if scalar == ScalarType2.UINT8: + return 1 + elif scalar == ScalarType2.UINT16: + return 2 + elif scalar == ScalarType2.UINT32: + return 4 + elif scalar == ScalarType2.UINT64: + return 8 + elif scalar == ScalarType2.INT8: + return 1 + elif scalar == ScalarType2.INT16: + return 2 + elif scalar == ScalarType2.INT32: + return 4 + elif scalar == ScalarType2.INT64: + return 8 + elif scalar == ScalarType2.FLOAT32: + return 4 + elif scalar == ScalarType2.FLOAT64: + return 8 + elif scalar == ScalarType2.FLOAT16: + return 2 + elif scalar == ScalarType2.STRING8: + return 8 + else: + raise ValueError(f"Invalid scalar type: {scalar}") + +def _customize_segy_spec(segy_spec: SegySpec) -> SegySpec: + assigned_bytes = [] + for field in segy_spec.trace.header.fields: + byte = field.byte-1 + for i in range(byte, byte + _scalar_to_size(field.format)): + assigned_bytes.append(i) + unassigned_bytes = [i for i in range(240) if i not in assigned_bytes] + field_to_customize = [HeaderField(name=f"Field_{i}", format=ScalarType.UINT8, byte=i+1) for i in unassigned_bytes] + segy_spec = segy_spec.customize(trace_header_fields=field_to_customize) + return segy_spec + def _add_raw_headers_to_template(mdio_template: AbstractDatasetTemplate) -> AbstractDatasetTemplate: """Add raw headers capability to the MDIO template by monkey-patching its _add_variables method. @@ -421,6 +462,9 @@ def segy_to_mdio( # noqa PLR0913 input_path = _normalize_path(input_path) output_path = _normalize_path(output_path) + if os.getenv("MDIO__DO_RAW_HEADERS") == "1": + segy_spec = _customize_segy_spec(segy_spec) + if not overwrite and output_path.exists(): err = f"Output location '{output_path.as_posix()}' exists. Set `overwrite=True` if intended." raise FileExistsError(err) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 8d4af5f4..5c82b465 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -148,25 +148,8 @@ def trace_worker( # noqa: PLR0913 ds_to_write = dataset[worker_variables] if raw_header_key in worker_variables: - # from segy.schema import HeaderField, ScalarType - from segy.schema import HeaderField - from segy.schema import ScalarType as ScalarType2 - - new_spec = deepcopy(segy_file.spec) - new_spec.trace.header.fields = [] - new_fields = [] - - for i in range(240): - new_fields.append(HeaderField(name=f"Field_{i}", format=ScalarType.UINT8, byte=i+1)) - - # # new_spec.trace.header.fields = new_fields - new_spec = new_spec.customize(trace_header_fields=new_fields) - updated_segy_file = SegyFile(segy_file.url, spec=new_spec) - updated_traces = updated_segy_file.trace[live_trace_indexes] - tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - # _foo = traces.tobytes()[:240] # _asContiguousCopy = np.ascontiguousarray(traces.header.copy()).view("|V240") @@ -185,8 +168,6 @@ def trace_worker( # noqa: PLR0913 # _aahhhhhhh = segy_file.header[0] - - # _asBytes = traces.header.tobytes() # _are_equal = _foo == _asBytes[:240] # _asBytesLen = len(_asBytes) @@ -195,18 +176,18 @@ def trace_worker( # noqa: PLR0913 # _asContiguousBytesView = np.ascontiguousarray(traces.header[0]).tobytes().view("|V240") # _asBuffer = memoryview(traces.header) - # _type = traces.header.dtype # tmp_raw_headers[not_null] = traces.raw_header - # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") + # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy(order="C")).view("|V240") + tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") # tmp_raw_headers[not_null] = _asContiguousBytes # tmp_raw_headers[not_null] = _asBytes # tmp_raw_headers[not_null] = traces.header.view("|V240") # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") # Leaks numpy metadata - tmp_raw_headers[not_null] = updated_traces.header.view("|V240") + # tmp_raw_headers[not_null] = updated_traces.header.view("|V240") ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index b58c4068..3e96e8a6 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -547,7 +547,6 @@ def read_segy_trace_header(trace_index: int) -> bytes: segy_trace_idx = 0 flat_mask = trace_mask.ravel() flat_raw_headers = raw_headers_data.ravel() # Flatten to 1D array of 240-byte header records - print(f"Flat mask shape: {flat_mask.shape}") operation = 'w' From a8f795220dcaa6d58f6240a2aa7ea5cc95c598fd Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 16:20:39 +0000 Subject: [PATCH 24/39] Cleanup --- src/mdio/segy/_workers.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 5c82b465..6ec17d7d 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -149,45 +149,7 @@ def trace_worker( # noqa: PLR0913 if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - - # _foo = traces.tobytes()[:240] - - # _asContiguousCopy = np.ascontiguousarray(traces.header.copy()).view("|V240") - # _asContiguousCopy1 = _asContiguousCopy[0] - - # _asContiguousNoCopy = np.ascontiguousarray(traces.header).view("|V240") - # _asContiguousNoCopy1 = _asContiguousNoCopy[0] - - # _asArrayCopy = traces.header.copy().view("|V240") - # _asArrayCopy1 = _asArrayCopy[0] - - # _asArrayNoCopy = traces.header.view("|V240") - # _asArrayNoCopy1 = _asArrayNoCopy[0] - - # _fo = type(traces.header) - - # _aahhhhhhh = segy_file.header[0] - - # _asBytes = traces.header.tobytes() - # _are_equal = _foo == _asBytes[:240] - # _asBytesLen = len(_asBytes) - # _asContiguousBytes = np.ascontiguousarray(traces.header).tobytes() - # _asContiguousBytesLen = len(_asContiguousBytes) - # _asContiguousBytesView = np.ascontiguousarray(traces.header[0]).tobytes().view("|V240") - # _asBuffer = memoryview(traces.header) - - # _type = traces.header.dtype - - # tmp_raw_headers[not_null] = traces.raw_header - # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy(order="C")).view("|V240") tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") - # tmp_raw_headers[not_null] = _asContiguousBytes - # tmp_raw_headers[not_null] = _asBytes - # tmp_raw_headers[not_null] = traces.header.view("|V240") - # tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") # Leaks numpy metadata - - - # tmp_raw_headers[not_null] = updated_traces.header.view("|V240") ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, @@ -214,7 +176,6 @@ def trace_worker( # noqa: PLR0913 encoding=ds_to_write[header_key].encoding, # Not strictly necessary, but safer than not doing it. ) - # del raw_headers # Manage memory 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) From f7d7c2c7b4f35686ef409dea4087bddfd0685075 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 16:36:12 +0000 Subject: [PATCH 25/39] Bandaid fix --- src/mdio/converters/segy.py | 10 ++++++--- src/mdio/segy/creation.py | 41 +++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index 3d99fa61..50e02012 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -371,15 +371,19 @@ def _scalar_to_size(scalar: ScalarType2) -> int: raise ValueError(f"Invalid scalar type: {scalar}") def _customize_segy_spec(segy_spec: SegySpec) -> SegySpec: + from copy import deepcopy assigned_bytes = [] + + ret = deepcopy(segy_spec) + for field in segy_spec.trace.header.fields: byte = field.byte-1 for i in range(byte, byte + _scalar_to_size(field.format)): assigned_bytes.append(i) unassigned_bytes = [i for i in range(240) if i not in assigned_bytes] - field_to_customize = [HeaderField(name=f"Field_{i}", format=ScalarType.UINT8, byte=i+1) for i in unassigned_bytes] - segy_spec = segy_spec.customize(trace_header_fields=field_to_customize) - return segy_spec + field_to_customize = [HeaderField(name=f"__MDIO_RAW_UNSPECIFIED_Field_{i}", format=ScalarType.UINT8, byte=i+1) for i in unassigned_bytes] + ret = ret.customize(trace_header_fields=field_to_customize) + return ret def _add_raw_headers_to_template(mdio_template: AbstractDatasetTemplate) -> AbstractDatasetTemplate: diff --git a/src/mdio/segy/creation.py b/src/mdio/segy/creation.py index 8b10ad48..81cfd13a 100644 --- a/src/mdio/segy/creation.py +++ b/src/mdio/segy/creation.py @@ -28,6 +28,39 @@ logger = logging.getLogger(__name__) +def _filter_raw_unspecified_fields(headers: NDArray) -> NDArray: + """Filter out __MDIO_RAW_UNSPECIFIED_Field_* fields from headers array. + + These fields are added during SEGY import to preserve raw header bytes, + but they cause dtype mismatches during export. This function removes them. + + Args: + headers: Header array that may contain raw unspecified fields. + + Returns: + Header array with raw unspecified fields removed. + """ + if headers.dtype.names is None: + return headers + + # Find field names that don't start with __MDIO_RAW_UNSPECIFIED_ + field_names = [name for name in headers.dtype.names + if not name.startswith("__MDIO_RAW_UNSPECIFIED_")] + + if len(field_names) == len(headers.dtype.names): + # No raw unspecified fields found, return as-is + return headers + + # Create new structured array with only the non-raw fields + new_dtype = [(name, headers.dtype.fields[name][0]) for name in field_names] + filtered_headers = np.empty(headers.shape, dtype=new_dtype) + + for name in field_names: + filtered_headers[name] = headers[name] + + return filtered_headers + + def make_segy_factory(spec: SegySpec, binary_header: dict[str, int]) -> SegyFactory: """Generate SEG-Y factory from MDIO metadata.""" sample_interval = binary_header["sample_interval"] @@ -167,7 +200,9 @@ def serialize_to_segy_stack( # noqa: PLR0913 samples = samples[live_mask] headers = headers[live_mask] - buffer = segy_factory.create_traces(headers, samples) + # Filter out raw unspecified fields that cause dtype mismatches + filtered_headers = _filter_raw_unspecified_fields(headers) + buffer = segy_factory.create_traces(filtered_headers, samples) global_index = block_start[0] record_id_str = str(global_index) @@ -199,7 +234,9 @@ def serialize_to_segy_stack( # noqa: PLR0913 rec_samples = samples[rec_index][rec_live_mask] rec_headers = headers[rec_index][rec_live_mask] - buffer = segy_factory.create_traces(rec_headers, rec_samples) + # Filter out raw unspecified fields that cause dtype mismatches + filtered_headers = _filter_raw_unspecified_fields(rec_headers) + buffer = segy_factory.create_traces(filtered_headers, rec_samples) global_index = tuple(block_start[i] + rec_index[i] for i in range(record_ndim)) record_id_str = "/".join(map(str, global_index)) From c6a277f71a7b189b8adbeab9620d280f02414b2f Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 18:31:32 +0000 Subject: [PATCH 26/39] linting pass 1 --- disaster_recovery_analysis/bootstrap.sh | 2 +- .../ingest_both_teapots.py | 14 ++-- disaster_recovery_analysis/src/main.rs | 60 ++++++++-------- src/mdio/converters/segy.py | 48 ++++--------- src/mdio/segy/_disaster_recovery_wrapper.py | 11 ++- src/mdio/segy/_workers.py | 2 +- src/mdio/segy/creation.py | 19 +++-- .../test_segy_import_export_masked.py | 70 +++++++++++-------- tests/unit/test_disaster_recovery_wrapper.py | 23 ++---- 9 files changed, 114 insertions(+), 135 deletions(-) diff --git a/disaster_recovery_analysis/bootstrap.sh b/disaster_recovery_analysis/bootstrap.sh index a6557a2b..cd5c8d85 100755 --- a/disaster_recovery_analysis/bootstrap.sh +++ b/disaster_recovery_analysis/bootstrap.sh @@ -59,7 +59,7 @@ if [ $# -eq 1 ]; then ZARR_PATH="$1" echo "=== Running hexdump on provided path: $ZARR_PATH ===" echo - + if [ -e "$ZARR_PATH" ]; then ./target/release/zarr-hexdump "$ZARR_PATH" else diff --git a/disaster_recovery_analysis/ingest_both_teapots.py b/disaster_recovery_analysis/ingest_both_teapots.py index 04aa52f5..fa923a87 100644 --- a/disaster_recovery_analysis/ingest_both_teapots.py +++ b/disaster_recovery_analysis/ingest_both_teapots.py @@ -1,11 +1,13 @@ if __name__ == "__main__": - import mdio - from segy.standards import get_segy_standard - from segy.schema import HeaderField, Endianness + import logging import os - from mdio.builder.template_registry import TemplateRegistry - import logging + from segy.schema import Endianness + from segy.schema import HeaderField + from segy.standards import get_segy_standard + + import mdio + from mdio.builder.template_registry import TemplateRegistry logging.getLogger("segy").setLevel(logging.DEBUG) @@ -40,4 +42,4 @@ input_path="filt_mig_IEEE_LittleEndian_Rev1.sgy", output_path="filt_mig_IEEE_LittleEndian_Rev1.mdio", overwrite=True, - ) \ No newline at end of file + ) diff --git a/disaster_recovery_analysis/src/main.rs b/disaster_recovery_analysis/src/main.rs index 974e3808..9cbbffb8 100644 --- a/disaster_recovery_analysis/src/main.rs +++ b/disaster_recovery_analysis/src/main.rs @@ -8,32 +8,32 @@ fn decompress_blosc(compressed_data: &[u8]) -> Result, String> { let mut nbytes = 0usize; let mut cbytes = 0usize; let mut blocksize = 0usize; - + blosc_sys::blosc_cbuffer_sizes( compressed_data.as_ptr() as *const std::ffi::c_void, &mut nbytes as *mut usize, &mut cbytes as *mut usize, &mut blocksize as *mut usize, ); - + if nbytes == 0 { return Err("Invalid compressed data".to_string()); } - + // Allocate output buffer let mut decompressed = vec![0u8; nbytes]; - + // Decompress let result = blosc_sys::blosc_decompress( compressed_data.as_ptr() as *const std::ffi::c_void, decompressed.as_mut_ptr() as *mut std::ffi::c_void, nbytes, ); - + if result < 0 { return Err(format!("Blosc decompression failed with code: {}", result)); } - + decompressed.truncate(result as usize); Ok(decompressed) } @@ -43,10 +43,10 @@ fn print_hexdump(data: &[u8], offset: usize, chunk_name: &str) { println!("=== {} ===", chunk_name); for (i, chunk) in data.chunks(16).enumerate() { let addr = offset + i * 16; - + // Print address print!("{:08x} ", addr); - + // Print hex bytes for (j, &byte) in chunk.iter().enumerate() { if j == 8 { @@ -54,7 +54,7 @@ fn print_hexdump(data: &[u8], offset: usize, chunk_name: &str) { } print!("{:02x} ", byte); } - + // Pad if chunk is less than 16 bytes if chunk.len() < 16 { for j in chunk.len()..16 { @@ -64,7 +64,7 @@ fn print_hexdump(data: &[u8], offset: usize, chunk_name: &str) { print!(" "); } } - + // Print ASCII representation print!(" |"); for &byte in chunk { @@ -81,38 +81,38 @@ fn print_hexdump(data: &[u8], offset: usize, chunk_name: &str) { fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); - + if args.len() != 2 { eprintln!("Usage: {} ", args[0]); eprintln!("Example: {} /path/to/zarr/array", args[0]); std::process::exit(1); } - + let zarr_path = Path::new(&args[1]); - + // Verify the path exists if !zarr_path.exists() { eprintln!("Error: Path '{}' does not exist", zarr_path.display()); std::process::exit(1); } - + println!("Reading Zarr array from: {}", zarr_path.display()); println!("========================================"); - + // Read zarr.json metadata let zarr_json_path = zarr_path.join("zarr.json"); if !zarr_json_path.exists() { eprintln!("Error: zarr.json not found in {}", zarr_path.display()); std::process::exit(1); } - + let metadata_content = fs::read_to_string(&zarr_json_path)?; let metadata: serde_json::Value = serde_json::from_str(&metadata_content)?; - + // Extract information from metadata let shape = metadata["shape"].as_array().unwrap(); let chunk_shape = metadata["chunk_grid"]["configuration"]["chunk_shape"].as_array().unwrap(); - + println!("Array shape: {:?}", shape); println!("Chunk shape: {:?}", chunk_shape); println!("Data type: {}", metadata["data_type"]["name"]); @@ -122,42 +122,42 @@ fn main() -> Result<(), Box> { } } println!(); - + // Calculate expected chunks based on the metadata we know: // Shape: [345, 188], Chunk shape: [128, 128] // This means we have ceil(345/128) = 3 chunks in dimension 0 // and ceil(188/128) = 2 chunks in dimension 1 // So we expect chunks: c/0/0, c/0/1, c/1/0, c/1/1, c/2/0, c/2/1 - + let mut chunk_files = Vec::new(); - + // Find all chunk files by walking the directory for entry in WalkDir::new(zarr_path) { let entry = entry?; let path = entry.path(); - + // Look for chunk files (they start with 'c/' in Zarr v3) if path.is_file() { let relative_path = path.strip_prefix(zarr_path)?; let path_str = relative_path.to_string_lossy(); - + if path_str.starts_with("c/") { chunk_files.push((path.to_path_buf(), path_str.to_string())); } } } - + // Sort chunk files for consistent ordering chunk_files.sort_by(|a, b| a.1.cmp(&b.1)); - + println!("Found {} chunk files:", chunk_files.len()); for (_, chunk_name) in &chunk_files { println!(" {}", chunk_name); } println!(); - + let mut total_offset = 0; - + // Read, decompress, and hexdump each chunk file for (chunk_path, chunk_name) in chunk_files { match fs::read(&chunk_path) { @@ -168,7 +168,7 @@ fn main() -> Result<(), Box> { println!(); } else { println!("Compressed size: {} bytes", compressed_data.len()); - + // Decompress the Blosc-compressed data using blosc-sys directly match decompress_blosc(&compressed_data) { Ok(decompressed_data) => { @@ -190,11 +190,11 @@ fn main() -> Result<(), Box> { } } } - + println!("Total decompressed bytes processed: {}", total_offset); println!(); println!("Note: This shows the decompressed array data as it would appear in memory."); println!("Each element is 240 bytes (raw_bytes with length_bytes: 240)."); - + Ok(()) } diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index 50e02012..22ea6325 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -4,16 +4,17 @@ import logging import os +from copy import deepcopy from typing import TYPE_CHECKING import numpy as np import zarr from segy import SegyFile from segy.config import SegySettings -from segy.standards.codes import MeasurementSystem as segy_MeasurementSystem -from segy.standards.fields.trace import Rev0 as TraceHeaderFieldsRev0 from segy.schema import HeaderField from segy.schema import ScalarType as ScalarType2 +from segy.standards.codes import MeasurementSystem as segy_MeasurementSystem +from segy.standards.fields.trace import Rev0 as TraceHeaderFieldsRev0 from mdio.api.io import _normalize_path from mdio.api.io import to_mdio @@ -342,48 +343,29 @@ def _add_grid_override_to_metadata(dataset: Dataset, grid_overrides: dict[str, A if grid_overrides is not None: dataset.metadata.attributes["gridOverrides"] = grid_overrides + def _scalar_to_size(scalar: ScalarType2) -> int: - if scalar == ScalarType2.UINT8: - return 1 - elif scalar == ScalarType2.UINT16: - return 2 - elif scalar == ScalarType2.UINT32: - return 4 - elif scalar == ScalarType2.UINT64: + if scalar == ScalarType2.STRING8: return 8 - elif scalar == ScalarType2.INT8: - return 1 - elif scalar == ScalarType2.INT16: - return 2 - elif scalar == ScalarType2.INT32: - return 4 - elif scalar == ScalarType2.INT64: - return 8 - elif scalar == ScalarType2.FLOAT32: - return 4 - elif scalar == ScalarType2.FLOAT64: - return 8 - elif scalar == ScalarType2.FLOAT16: - return 2 - elif scalar == ScalarType2.STRING8: - return 8 - else: - raise ValueError(f"Invalid scalar type: {scalar}") + + return str(scalar).split(".")[1] % 8 + def _customize_segy_spec(segy_spec: SegySpec) -> SegySpec: - from copy import deepcopy assigned_bytes = [] ret = deepcopy(segy_spec) for field in segy_spec.trace.header.fields: - byte = field.byte-1 + byte = field.byte - 1 for i in range(byte, byte + _scalar_to_size(field.format)): - assigned_bytes.append(i) + assigned_bytes.append(i) # noqa: PERF402 unassigned_bytes = [i for i in range(240) if i not in assigned_bytes] - field_to_customize = [HeaderField(name=f"__MDIO_RAW_UNSPECIFIED_Field_{i}", format=ScalarType.UINT8, byte=i+1) for i in unassigned_bytes] - ret = ret.customize(trace_header_fields=field_to_customize) - return ret + field_to_customize = [ + HeaderField(name=f"__MDIO_RAW_UNSPECIFIED_Field_{i}", format=ScalarType.UINT8, byte=i + 1) + for i in unassigned_bytes + ] + return ret.customize(trace_header_fields=field_to_customize) def _add_raw_headers_to_template(mdio_template: AbstractDatasetTemplate) -> AbstractDatasetTemplate: diff --git a/src/mdio/segy/_disaster_recovery_wrapper.py b/src/mdio/segy/_disaster_recovery_wrapper.py index 8b4afb46..aaa64773 100644 --- a/src/mdio/segy/_disaster_recovery_wrapper.py +++ b/src/mdio/segy/_disaster_recovery_wrapper.py @@ -2,17 +2,17 @@ from __future__ import annotations +from copy import deepcopy from typing import TYPE_CHECKING -from copy import deepcopy import numpy as np if TYPE_CHECKING: from numpy.typing import NDArray from segy import SegyFile -class SegyFileTraceDataWrapper: +class SegyFileTraceDataWrapper: def __init__(self, segy_file: SegyFile, indices: int | list[int] | NDArray | slice): self.segy_file = segy_file self.indices = indices @@ -21,15 +21,14 @@ def __init__(self, segy_file: SegyFile, indices: int | list[int] | NDArray | sli self.traces = segy_file.trace[indices] @property - def header(self): + def header(self) -> NDArray: # The copy is necessary to avoid applying the pipeline to the original header. return self._header_pipeline.apply(self.traces.header.copy()) - @property - def raw_header(self): + def raw_header(self) -> NDArray: return np.ascontiguousarray(self.traces.header.copy()).view("|V240") @property - def sample(self): + def sample(self) -> NDArray: return self.traces.sample diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index 6ec17d7d..f960811e 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -12,7 +12,6 @@ from mdio.api.io import to_mdio from mdio.builder.schemas.dtype import ScalarType -from mdio.segy._disaster_recovery_wrapper import SegyFileTraceDataWrapper if TYPE_CHECKING: from segy.arrays import HeaderArray @@ -141,6 +140,7 @@ def trace_worker( # noqa: PLR0913 # NOTE: The `raw_header_key` code block should be removed in full as it will become dead code. # traces = SegyFileTraceDataWrapper(segy_file, live_trace_indexes) from copy import deepcopy + header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) segy_file.accessors.header_decode_pipeline.transforms = [] traces = segy_file.trace[live_trace_indexes] diff --git a/src/mdio/segy/creation.py b/src/mdio/segy/creation.py index 81cfd13a..4c5e25a9 100644 --- a/src/mdio/segy/creation.py +++ b/src/mdio/segy/creation.py @@ -30,34 +30,33 @@ def _filter_raw_unspecified_fields(headers: NDArray) -> NDArray: """Filter out __MDIO_RAW_UNSPECIFIED_Field_* fields from headers array. - + These fields are added during SEGY import to preserve raw header bytes, but they cause dtype mismatches during export. This function removes them. - + Args: headers: Header array that may contain raw unspecified fields. - + Returns: Header array with raw unspecified fields removed. """ if headers.dtype.names is None: return headers - + # Find field names that don't start with __MDIO_RAW_UNSPECIFIED_ - field_names = [name for name in headers.dtype.names - if not name.startswith("__MDIO_RAW_UNSPECIFIED_")] - + field_names = [name for name in headers.dtype.names if not name.startswith("__MDIO_RAW_UNSPECIFIED_")] + if len(field_names) == len(headers.dtype.names): # No raw unspecified fields found, return as-is return headers - + # Create new structured array with only the non-raw fields new_dtype = [(name, headers.dtype.fields[name][0]) for name in field_names] filtered_headers = np.empty(headers.shape, dtype=new_dtype) - + for name in field_names: filtered_headers[name] = headers[name] - + return filtered_headers diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 3e96e8a6..71be5e65 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -288,7 +288,7 @@ def export_masked_path(tmp_path_factory: pytest.TempPathFactory, raw_headers_env # raw_headers_env dependency ensures the environment variable is set before this runs raw_headers_enabled = os.getenv("MDIO__DO_RAW_HEADERS") == "1" path_suffix = "with_raw_headers" if raw_headers_enabled else "without_raw_headers" - + if DEBUG_MODE: return Path(f"TMP/export_masked_{path_suffix}") return tmp_path_factory.getbasetemp() / f"export_masked_{path_suffix}" @@ -307,16 +307,23 @@ def raw_headers_env(request: pytest.FixtureRequest) -> None: # Cleanup after test - both environment variable and template state os.environ.pop("MDIO__DO_RAW_HEADERS", None) - + # Clean up any template modifications to ensure test isolation from mdio.builder.template_registry import TemplateRegistry + registry = TemplateRegistry.get_instance() - + # Reset any templates that might have been modified with raw headers - template_names = ["PostStack2DTime", "PostStack3DTime", "PreStackCdpOffsetGathers2DTime", - "PreStackCdpOffsetGathers3DTime", "PreStackShotGathers2DTime", - "PreStackShotGathers3DTime", "PreStackCocaGathers3DTime"] - + template_names = [ + "PostStack2DTime", + "PostStack3DTime", + "PreStackCdpOffsetGathers2DTime", + "PreStackCdpOffsetGathers3DTime", + "PreStackShotGathers2DTime", + "PreStackShotGathers3DTime", + "PreStackCocaGathers3DTime", + ] + for template_name in template_names: try: template = registry.get(template_name) @@ -327,7 +334,7 @@ def raw_headers_env(request: pytest.FixtureRequest) -> None: # We need to restore it to the original method from the class # Since we can't easily restore the exact original, we'll get a fresh instance template_class = type(template) - if hasattr(template_class, '_add_variables'): + if hasattr(template_class, "_add_variables"): template._add_variables = template_class._add_variables.__get__(template, template_class) except KeyError: # Template not found, skip @@ -523,18 +530,20 @@ def test_raw_headers_byte_preservation( else: assert not has_raw_headers, f"raw_headers should not be present when MDIO__DO_RAW_HEADERS is not set\n {ds}" return # Exit early if raw_headers are not expected - + # Get data (only if raw_headers exist) raw_headers_data = ds.raw_headers.values trace_mask = ds.trace_mask.values - + # Verify 240-byte headers - assert raw_headers_data.dtype.itemsize == 240, f"Expected 240-byte headers, got {raw_headers_data.dtype.itemsize}" - + assert raw_headers_data.dtype.itemsize == 240, ( + f"Expected 240-byte headers, got {raw_headers_data.dtype.itemsize}" + ) + # Read raw bytes directly from SEG-Y file def read_segy_trace_header(trace_index: int) -> bytes: """Read 240-byte trace header directly from SEG-Y file.""" - with open(segy_path, 'rb') as f: + with open(segy_path, "rb") as f: # Skip text (3200) + binary (400) headers = 3600 bytes f.seek(3600) # Each trace: 240 byte header + (num_samples * 4) byte samples @@ -542,41 +551,41 @@ def read_segy_trace_header(trace_index: int) -> bytes: trace_offset = trace_index * trace_size f.seek(trace_offset, 1) # Seek relative to current position return f.read(240) - + # Compare all valid traces byte-by-byte segy_trace_idx = 0 flat_mask = trace_mask.ravel() flat_raw_headers = raw_headers_data.ravel() # Flatten to 1D array of 240-byte header records - operation = 'w' - + operation = "w" + for grid_idx in range(flat_mask.size): if not flat_mask[grid_idx]: print(f"Skipping trace {grid_idx} because it is masked") continue - + # Get MDIO header as bytes - convert single header record to bytes header_record = flat_raw_headers[grid_idx] mdio_header_bytes = np.frombuffer(header_record.tobytes(), dtype=np.uint8) - + # Get SEG-Y header as raw bytes directly from file segy_raw_header_bytes = read_segy_trace_header(segy_trace_idx) segy_header_bytes = np.frombuffer(segy_raw_header_bytes, dtype=np.uint8) assert_array_equal(mdio_header_bytes, segy_header_bytes) - + # Compare byte-by-byte # Write hexdumps to separate files for analysis # def hexdump_to_string(data: bytes, title: str) -> str: # """Create hexdump string.""" # lines = [f"{title}", "=" * len(title), ""] - + # for i in range(0, len(data), 16): # # Address # addr = i # hex_part = "" # ascii_part = "" - + # # Process 16 bytes at a time # for j in range(16): # if i + j < len(data): @@ -586,25 +595,25 @@ def read_segy_trace_header(trace_index: int) -> bytes: # else: # hex_part += " " # ascii_part += " " - + # lines.append(f"{addr:08x}: {hex_part} |{ascii_part}|") - + # return "\n".join(lines) - + # # Generate filenames for this test case # segy_filename = f"segy_headers_{grid_conf.name}.txt" # mdio_filename = f"mdio_headers_{grid_conf.name}.txt" - + # # Append SEG-Y hexdump to file # with open(segy_filename, operation) as f: # if segy_trace_idx == 0: # f.write("") # Start fresh for first trace # else: # f.write("\n\n") # Add spacing between traces - # f.write(hexdump_to_string(segy_header_bytes, + # f.write(hexdump_to_string(segy_header_bytes, # f"SEG-Y Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) - - # # Append MDIO hexdump to file + + # # Append MDIO hexdump to file # with open(mdio_filename, operation) as f: # if segy_trace_idx == 0: # f.write("") # Start fresh for first trace @@ -613,11 +622,10 @@ def read_segy_trace_header(trace_index: int) -> bytes: # f.write(hexdump_to_string(mdio_header_bytes, # f"MDIO Raw Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) # operation = 'a' - + # if segy_trace_idx == 0: # print(f"\nHeader hexdumps being written for {grid_conf.name}:") # print(f" SEG-Y: {segy_filename}") # print(f" MDIO: {mdio_filename}") - - + segy_trace_idx += 1 diff --git a/tests/unit/test_disaster_recovery_wrapper.py b/tests/unit/test_disaster_recovery_wrapper.py index e4e3e31f..bb1864f6 100644 --- a/tests/unit/test_disaster_recovery_wrapper.py +++ b/tests/unit/test_disaster_recovery_wrapper.py @@ -9,7 +9,6 @@ import tempfile from pathlib import Path -from typing import TYPE_CHECKING import numpy as np import pytest @@ -22,9 +21,6 @@ from mdio.segy._disaster_recovery_wrapper import SegyFileTraceDataWrapper -if TYPE_CHECKING: - from numpy.typing import NDArray - SAMPLES_PER_TRACE = 1501 @@ -118,10 +114,7 @@ def create_test_segy_file( # noqa: PLR0913 return spec - - def test_wrapper_basic_functionality( - self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict - ) -> None: + def test_wrapper_basic_functionality(self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict) -> None: """Test basic functionality of SegyFileTraceDataWrapper.""" config_name = segy_config["name"] endianness = segy_config["endianness"] @@ -160,14 +153,12 @@ def test_wrapper_basic_functionality( # Raw header should be bytes (240 bytes per trace header) assert raw_header.dtype == np.dtype("|V240") - + # Transformed header should have the expected fields assert "inline" in transformed_header.dtype.names assert "crossline" in transformed_header.dtype.names - def test_wrapper_with_multiple_traces( - self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict - ) -> None: + def test_wrapper_with_multiple_traces(self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict) -> None: """Test wrapper with multiple traces.""" config_name = segy_config["name"] endianness = segy_config["endianness"] @@ -297,9 +288,7 @@ def test_different_index_types( assert wrapper.header.size == expected_count - def test_header_pipeline_preservation( - self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict - ) -> None: + def test_header_pipeline_preservation(self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict) -> None: """Test that the wrapper preserves the original header pipeline.""" config_name = segy_config["name"] endianness = segy_config["endianness"] @@ -322,7 +311,7 @@ def test_header_pipeline_preservation( # Load the SEGY file segy_file = SegyFile(segy_path, spec=spec) - + # Store original pipeline transforms count original_transforms_count = len(segy_file.accessors.header_decode_pipeline.transforms) @@ -331,6 +320,6 @@ def test_header_pipeline_preservation( # Verify that the original SEGY file's pipeline was modified (transforms cleared) assert len(segy_file.accessors.header_decode_pipeline.transforms) == 0 - + # Verify that the wrapper has its own pipeline with the original transforms assert len(wrapper._header_pipeline.transforms) == original_transforms_count From 5b826fe51cd84beb36d15910d0f05ff9cee401a4 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:11:39 +0000 Subject: [PATCH 27/39] Fix logic issue --- src/mdio/converters/segy.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index 22ea6325..d5403b9c 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -345,10 +345,13 @@ def _add_grid_override_to_metadata(dataset: Dataset, grid_overrides: dict[str, A def _scalar_to_size(scalar: ScalarType2) -> int: + # TODO(BrianMichell): #0000 Lazy way to support conversion. if scalar == ScalarType2.STRING8: return 8 - - return str(scalar).split(".")[1] % 8 + try: + return int(str(scalar)[-2:]) // 8 + except ValueError: + return 1 def _customize_segy_spec(segy_spec: SegySpec) -> SegySpec: From 1266be9d25ddca8fbb6edf235b6431ed64ddcddb Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:15:24 +0000 Subject: [PATCH 28/39] Use wrapper class --- src/mdio/segy/_workers.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/mdio/segy/_workers.py b/src/mdio/segy/_workers.py index f960811e..184aa488 100644 --- a/src/mdio/segy/_workers.py +++ b/src/mdio/segy/_workers.py @@ -12,6 +12,7 @@ from mdio.api.io import to_mdio from mdio.builder.schemas.dtype import ScalarType +from mdio.segy._disaster_recovery_wrapper import SegyFileTraceDataWrapper if TYPE_CHECKING: from segy.arrays import HeaderArray @@ -138,18 +139,13 @@ def trace_worker( # noqa: PLR0913 # For that reason, we have wrapped the accessors to provide an interface that can be removed # and not require additional changes to the below code. # NOTE: The `raw_header_key` code block should be removed in full as it will become dead code. - # traces = SegyFileTraceDataWrapper(segy_file, live_trace_indexes) - from copy import deepcopy - - header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) - segy_file.accessors.header_decode_pipeline.transforms = [] - traces = segy_file.trace[live_trace_indexes] + traces = SegyFileTraceDataWrapper(segy_file, live_trace_indexes) ds_to_write = dataset[worker_variables] if raw_header_key in worker_variables: tmp_raw_headers = np.zeros_like(dataset[raw_header_key]) - tmp_raw_headers[not_null] = np.ascontiguousarray(traces.header.copy()).view("|V240") + tmp_raw_headers[not_null] = traces.raw_header ds_to_write[raw_header_key] = Variable( ds_to_write[raw_header_key].dims, @@ -162,8 +158,7 @@ def trace_worker( # noqa: PLR0913 # TODO(BrianMichell): Implement this better so that we can enable fill values without changing the code # https://github.com/TGSAI/mdio-python/issues/584 tmp_headers = np.zeros_like(dataset[header_key]) - # tmp_headers[not_null] = traces.header - tmp_headers[not_null] = header_pipeline.apply(traces.header.copy()) + tmp_headers[not_null] = traces.header # Create a new Variable object to avoid copying the temporary array # The ideal solution is to use `ds_to_write[header_key][:] = tmp_headers` # but Xarray appears to be copying memory instead of doing direct assignment. From fd890fccff048702b9fe18966fcb61a384c7cb2c Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:19:18 +0000 Subject: [PATCH 29/39] Precommit --- tests/integration/test_segy_import_export_masked.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 71be5e65..023544b1 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -309,8 +309,6 @@ def raw_headers_env(request: pytest.FixtureRequest) -> None: os.environ.pop("MDIO__DO_RAW_HEADERS", None) # Clean up any template modifications to ensure test isolation - from mdio.builder.template_registry import TemplateRegistry - registry = TemplateRegistry.get_instance() # Reset any templates that might have been modified with raw headers @@ -543,7 +541,8 @@ def test_raw_headers_byte_preservation( # Read raw bytes directly from SEG-Y file def read_segy_trace_header(trace_index: int) -> bytes: """Read 240-byte trace header directly from SEG-Y file.""" - with open(segy_path, "rb") as f: + # with open(segy_path, "rb") as f: + with Path.open(segy_path, "rb") as f: # Skip text (3200) + binary (400) headers = 3600 bytes f.seek(3600) # Each trace: 240 byte header + (num_samples * 4) byte samples @@ -557,7 +556,7 @@ def read_segy_trace_header(trace_index: int) -> bytes: flat_mask = trace_mask.ravel() flat_raw_headers = raw_headers_data.ravel() # Flatten to 1D array of 240-byte header records - operation = "w" + # operation = "w" for grid_idx in range(flat_mask.size): if not flat_mask[grid_idx]: From e8027de254b55cc639cc3b2258add88ad65e4ce3 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:20:19 +0000 Subject: [PATCH 30/39] Remove external debugging code --- disaster_recovery_analysis/bootstrap.sh | 76 ------- .../ingest_both_teapots.py | 45 ---- disaster_recovery_analysis/src/main.rs | 200 ------------------ 3 files changed, 321 deletions(-) delete mode 100755 disaster_recovery_analysis/bootstrap.sh delete mode 100644 disaster_recovery_analysis/ingest_both_teapots.py delete mode 100644 disaster_recovery_analysis/src/main.rs diff --git a/disaster_recovery_analysis/bootstrap.sh b/disaster_recovery_analysis/bootstrap.sh deleted file mode 100755 index cd5c8d85..00000000 --- a/disaster_recovery_analysis/bootstrap.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash - -set -e # Exit on any error - -echo "=== Zarr Hexdump Bootstrap Script ===" -echo - -# Check if Rust is installed, if not install it -if ! command -v rustc &> /dev/null; then - echo "Rust is not installed. Installing Rust..." - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - source $HOME/.cargo/env - echo "✓ Rust installed successfully" -fi - -echo "✓ Rust is installed ($(rustc --version))" - -# Check if Cargo is available -if ! command -v cargo &> /dev/null; then - echo "Sourcing Rust environment..." - source $HOME/.cargo/env -fi - -echo "✓ Cargo is available ($(cargo --version))" -echo - -# Install blosc library if not present -echo "Checking for blosc library..." -if ! pkg-config --exists blosc; then - echo "Installing blosc library..." - sudo apt-get update - sudo apt-get install -y libblosc-dev pkg-config - echo "✓ Blosc library installed" -else - echo "✓ Blosc library already available" -fi -echo - -# Build the project -echo "Building the Zarr hexdump tool..." -if cargo build --release; then - echo "✓ Build successful!" -else - echo "✗ Build failed!" - exit 1 -fi - -echo -echo "=== Usage ===" -echo "To hexdump a Zarr array, run:" -echo " ./target/release/zarr-hexdump /path/to/your/zarr/array" -echo -echo "Or use cargo run:" -echo " cargo run --release -- /path/to/your/zarr/array" -echo - -# Check if a path was provided as an argument -if [ $# -eq 1 ]; then - ZARR_PATH="$1" - echo "=== Running hexdump on provided path: $ZARR_PATH ===" - echo - - if [ -e "$ZARR_PATH" ]; then - ./target/release/zarr-hexdump "$ZARR_PATH" - else - echo "Error: Path '$ZARR_PATH' does not exist!" - exit 1 - fi -elif [ $# -gt 1 ]; then - echo "Error: Too many arguments provided." - echo "Usage: $0 [zarr_array_path]" - exit 1 -else - echo "No Zarr array path provided. Build completed successfully." - echo "Use the commands above to run the hexdump tool." -fi diff --git a/disaster_recovery_analysis/ingest_both_teapots.py b/disaster_recovery_analysis/ingest_both_teapots.py deleted file mode 100644 index fa923a87..00000000 --- a/disaster_recovery_analysis/ingest_both_teapots.py +++ /dev/null @@ -1,45 +0,0 @@ -if __name__ == "__main__": - import logging - import os - - from segy.schema import Endianness - from segy.schema import HeaderField - from segy.standards import get_segy_standard - - import mdio - from mdio.builder.template_registry import TemplateRegistry - - logging.getLogger("segy").setLevel(logging.DEBUG) - - os.environ["MDIO__IMPORT__CLOUD_NATIVE"] = "true" - os.environ["MDIO__IMPORT__CPU_COUNT"] = "16" - os.environ["MDIO__DO_RAW_HEADERS"] = "1" - - custom_headers = [ - HeaderField(name="inline", byte=181, format="int32"), - HeaderField(name="crossline", byte=185, format="int32"), - HeaderField(name="cdp_x", byte=81, format="int32"), - HeaderField(name="cdp_y", byte=85, format="int32"), - ] - - big_endian_spec = get_segy_standard(0) - big_endian_spec.endianness = Endianness.BIG - little_endian_spec = get_segy_standard(0) - little_endian_spec.endianness = Endianness.LITTLE - big_endian_spec = big_endian_spec.customize(trace_header_fields=custom_headers) - little_endian_spec = little_endian_spec.customize(trace_header_fields=custom_headers) - - mdio.segy_to_mdio( - segy_spec=big_endian_spec, - mdio_template=TemplateRegistry().get("PostStack3DTime"), - input_path="filt_mig_IEEE_BigEndian_Rev1.sgy", - output_path="filt_mig_IEEE_BigEndian_Rev1.mdio", - overwrite=True, - ) - mdio.segy_to_mdio( - segy_spec=little_endian_spec, - mdio_template=TemplateRegistry().get("PostStack3DTime"), - input_path="filt_mig_IEEE_LittleEndian_Rev1.sgy", - output_path="filt_mig_IEEE_LittleEndian_Rev1.mdio", - overwrite=True, - ) diff --git a/disaster_recovery_analysis/src/main.rs b/disaster_recovery_analysis/src/main.rs deleted file mode 100644 index 9cbbffb8..00000000 --- a/disaster_recovery_analysis/src/main.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::env; -use std::fs; -use std::path::Path; -use walkdir::WalkDir; -fn decompress_blosc(compressed_data: &[u8]) -> Result, String> { - unsafe { - // Get decompressed size first - let mut nbytes = 0usize; - let mut cbytes = 0usize; - let mut blocksize = 0usize; - - blosc_sys::blosc_cbuffer_sizes( - compressed_data.as_ptr() as *const std::ffi::c_void, - &mut nbytes as *mut usize, - &mut cbytes as *mut usize, - &mut blocksize as *mut usize, - ); - - if nbytes == 0 { - return Err("Invalid compressed data".to_string()); - } - - // Allocate output buffer - let mut decompressed = vec![0u8; nbytes]; - - // Decompress - let result = blosc_sys::blosc_decompress( - compressed_data.as_ptr() as *const std::ffi::c_void, - decompressed.as_mut_ptr() as *mut std::ffi::c_void, - nbytes, - ); - - if result < 0 { - return Err(format!("Blosc decompression failed with code: {}", result)); - } - - decompressed.truncate(result as usize); - Ok(decompressed) - } -} - -fn print_hexdump(data: &[u8], offset: usize, chunk_name: &str) { - println!("=== {} ===", chunk_name); - for (i, chunk) in data.chunks(16).enumerate() { - let addr = offset + i * 16; - - // Print address - print!("{:08x} ", addr); - - // Print hex bytes - for (j, &byte) in chunk.iter().enumerate() { - if j == 8 { - print!(" "); // Extra space in the middle - } - print!("{:02x} ", byte); - } - - // Pad if chunk is less than 16 bytes - if chunk.len() < 16 { - for j in chunk.len()..16 { - if j == 8 { - print!(" "); - } - print!(" "); - } - } - - // Print ASCII representation - print!(" |"); - for &byte in chunk { - if byte >= 32 && byte <= 126 { - print!("{}", byte as char); - } else { - print!("."); - } - } - println!("|"); - } - println!(); -} - -fn main() -> Result<(), Box> { - let args: Vec = env::args().collect(); - - if args.len() != 2 { - eprintln!("Usage: {} ", args[0]); - eprintln!("Example: {} /path/to/zarr/array", args[0]); - std::process::exit(1); - } - - let zarr_path = Path::new(&args[1]); - - // Verify the path exists - if !zarr_path.exists() { - eprintln!("Error: Path '{}' does not exist", zarr_path.display()); - std::process::exit(1); - } - - println!("Reading Zarr array from: {}", zarr_path.display()); - println!("========================================"); - - // Read zarr.json metadata - let zarr_json_path = zarr_path.join("zarr.json"); - if !zarr_json_path.exists() { - eprintln!("Error: zarr.json not found in {}", zarr_path.display()); - std::process::exit(1); - } - - let metadata_content = fs::read_to_string(&zarr_json_path)?; - let metadata: serde_json::Value = serde_json::from_str(&metadata_content)?; - - // Extract information from metadata - let shape = metadata["shape"].as_array().unwrap(); - let chunk_shape = metadata["chunk_grid"]["configuration"]["chunk_shape"].as_array().unwrap(); - - println!("Array shape: {:?}", shape); - println!("Chunk shape: {:?}", chunk_shape); - println!("Data type: {}", metadata["data_type"]["name"]); - if let Some(config) = metadata["data_type"]["configuration"].as_object() { - if let Some(length_bytes) = config.get("length_bytes") { - println!("Length bytes: {}", length_bytes); - } - } - println!(); - - // Calculate expected chunks based on the metadata we know: - // Shape: [345, 188], Chunk shape: [128, 128] - // This means we have ceil(345/128) = 3 chunks in dimension 0 - // and ceil(188/128) = 2 chunks in dimension 1 - // So we expect chunks: c/0/0, c/0/1, c/1/0, c/1/1, c/2/0, c/2/1 - - let mut chunk_files = Vec::new(); - - // Find all chunk files by walking the directory - for entry in WalkDir::new(zarr_path) { - let entry = entry?; - let path = entry.path(); - - // Look for chunk files (they start with 'c/' in Zarr v3) - if path.is_file() { - let relative_path = path.strip_prefix(zarr_path)?; - let path_str = relative_path.to_string_lossy(); - - if path_str.starts_with("c/") { - chunk_files.push((path.to_path_buf(), path_str.to_string())); - } - } - } - - // Sort chunk files for consistent ordering - chunk_files.sort_by(|a, b| a.1.cmp(&b.1)); - - println!("Found {} chunk files:", chunk_files.len()); - for (_, chunk_name) in &chunk_files { - println!(" {}", chunk_name); - } - println!(); - - let mut total_offset = 0; - - // Read, decompress, and hexdump each chunk file - for (chunk_path, chunk_name) in chunk_files { - match fs::read(&chunk_path) { - Ok(compressed_data) => { - if compressed_data.is_empty() { - println!("=== {} ===", chunk_name); - println!("(empty chunk)"); - println!(); - } else { - println!("Compressed size: {} bytes", compressed_data.len()); - - // Decompress the Blosc-compressed data using blosc-sys directly - match decompress_blosc(&compressed_data) { - Ok(decompressed_data) => { - println!("Decompressed size: {} bytes", decompressed_data.len()); - print_hexdump(&decompressed_data, total_offset, &chunk_name); - total_offset += decompressed_data.len(); - } - Err(e) => { - eprintln!("Error decompressing chunk {}: {}", chunk_name, e); - println!("Showing raw compressed data instead:"); - print_hexdump(&compressed_data, total_offset, &format!("{} (compressed)", chunk_name)); - total_offset += compressed_data.len(); - } - } - } - } - Err(e) => { - eprintln!("Error reading chunk {}: {}", chunk_name, e); - } - } - } - - println!("Total decompressed bytes processed: {}", total_offset); - println!(); - println!("Note: This shows the decompressed array data as it would appear in memory."); - println!("Each element is 240 bytes (raw_bytes with length_bytes: 240)."); - - Ok(()) -} From 97d313189687d05f1d76f9526cece023c7014914 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:27:08 +0000 Subject: [PATCH 31/39] Remove debug code --- .../test_segy_import_export_masked.py | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/tests/integration/test_segy_import_export_masked.py b/tests/integration/test_segy_import_export_masked.py index 023544b1..fcd0df46 100644 --- a/tests/integration/test_segy_import_export_masked.py +++ b/tests/integration/test_segy_import_export_masked.py @@ -556,8 +556,6 @@ def read_segy_trace_header(trace_index: int) -> bytes: flat_mask = trace_mask.ravel() flat_raw_headers = raw_headers_data.ravel() # Flatten to 1D array of 240-byte header records - # operation = "w" - for grid_idx in range(flat_mask.size): if not flat_mask[grid_idx]: print(f"Skipping trace {grid_idx} because it is masked") @@ -573,58 +571,4 @@ def read_segy_trace_header(trace_index: int) -> bytes: assert_array_equal(mdio_header_bytes, segy_header_bytes) - # Compare byte-by-byte - # Write hexdumps to separate files for analysis - # def hexdump_to_string(data: bytes, title: str) -> str: - # """Create hexdump string.""" - # lines = [f"{title}", "=" * len(title), ""] - - # for i in range(0, len(data), 16): - # # Address - # addr = i - # hex_part = "" - # ascii_part = "" - - # # Process 16 bytes at a time - # for j in range(16): - # if i + j < len(data): - # byte_val = data[i + j] - # hex_part += f"{byte_val:02x} " - # ascii_part += chr(byte_val) if 32 <= byte_val <= 126 else "." - # else: - # hex_part += " " - # ascii_part += " " - - # lines.append(f"{addr:08x}: {hex_part} |{ascii_part}|") - - # return "\n".join(lines) - - # # Generate filenames for this test case - # segy_filename = f"segy_headers_{grid_conf.name}.txt" - # mdio_filename = f"mdio_headers_{grid_conf.name}.txt" - - # # Append SEG-Y hexdump to file - # with open(segy_filename, operation) as f: - # if segy_trace_idx == 0: - # f.write("") # Start fresh for first trace - # else: - # f.write("\n\n") # Add spacing between traces - # f.write(hexdump_to_string(segy_header_bytes, - # f"SEG-Y Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) - - # # Append MDIO hexdump to file - # with open(mdio_filename, operation) as f: - # if segy_trace_idx == 0: - # f.write("") # Start fresh for first trace - # else: - # f.write("\n\n") # Add spacing between traces - # f.write(hexdump_to_string(mdio_header_bytes, - # f"MDIO Raw Header - {grid_conf.name} Trace {segy_trace_idx} (240 bytes)")) - # operation = 'a' - - # if segy_trace_idx == 0: - # print(f"\nHeader hexdumps being written for {grid_conf.name}:") - # print(f" SEG-Y: {segy_filename}") - # print(f" MDIO: {mdio_filename}") - segy_trace_idx += 1 From aab7e69bc5e5147d913093aba0dae2ca96024e31 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:39:34 +0000 Subject: [PATCH 32/39] Remove errant numpy additon to pyproject toml --- pyproject.toml | 1 - uv.lock | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 311da2b7..479a6220 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ dependencies = [ "click-params>=0.5.0", "dask>=2025.9.0", "fsspec>=2025.9.0", - "numpy>=2.3.3", "pint>=0.25.0", "psutil>=7.0.0", "pydantic>=2.11.9", diff --git a/uv.lock b/uv.lock index d654c465..d03906d4 100644 --- a/uv.lock +++ b/uv.lock @@ -1863,7 +1863,6 @@ dependencies = [ { name = "click-params" }, { name = "dask" }, { name = "fsspec" }, - { name = "numpy" }, { name = "pint" }, { name = "psutil" }, { name = "pydantic" }, @@ -1926,7 +1925,6 @@ requires-dist = [ { name = "distributed", marker = "extra == 'distributed'", specifier = ">=2025.9.0" }, { name = "fsspec", specifier = ">=2025.9.0" }, { name = "gcsfs", marker = "extra == 'cloud'", specifier = ">=2025.9.0" }, - { name = "numpy", specifier = ">=2.3.3" }, { name = "pint", specifier = ">=0.25.0" }, { name = "psutil", specifier = ">=7.0.0" }, { name = "pydantic", specifier = ">=2.11.9" }, @@ -3137,26 +3135,32 @@ version = "0.2.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/e9/39ec4d4b3f91188fad1842748f67d4e749c77c37e353c4e545052ee8e893/ruamel.yaml.clib-0.2.14.tar.gz", hash = "sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e", size = 225394, upload-time = "2025-09-22T19:51:23.753Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/9f/3c51e9578b8c36fcc4bdd271a1a5bb65963a74a4b6ad1a989768a22f6c2a/ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e", size = 270207, upload-time = "2025-09-23T14:24:11.445Z" }, { url = "https://files.pythonhosted.org/packages/4a/16/cb02815bc2ae9c66760c0c061d23c7358f9ba51dae95ac85247662b7fbe2/ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d", size = 137780, upload-time = "2025-09-22T19:50:37.734Z" }, { url = "https://files.pythonhosted.org/packages/31/c6/fc687cd1b93bff8e40861eea46d6dc1a6a778d9a085684e4045ff26a8e40/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9", size = 641590, upload-time = "2025-09-22T19:50:41.978Z" }, { url = "https://files.pythonhosted.org/packages/45/5d/65a2bc08b709b08576b3f307bf63951ee68a8e047cbbda6f1c9864ecf9a7/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70", size = 738090, upload-time = "2025-09-22T19:50:39.152Z" }, { url = "https://files.pythonhosted.org/packages/fb/d0/a70a03614d9a6788a3661ab1538879ed2aae4e84d861f101243116308a37/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98", size = 700744, upload-time = "2025-09-22T19:50:40.811Z" }, + { url = "https://files.pythonhosted.org/packages/77/30/c93fa457611f79946d5cb6cc97493ca5425f3f21891d7b1f9b44eaa1b38e/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee", size = 742321, upload-time = "2025-09-23T18:42:48.916Z" }, { url = "https://files.pythonhosted.org/packages/40/85/e2c54ad637117cd13244a4649946eaa00f32edcb882d1f92df90e079ab00/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d", size = 743805, upload-time = "2025-09-22T19:50:43.58Z" }, { url = "https://files.pythonhosted.org/packages/81/50/f899072c38877d8ef5382e0b3d47f8c4346226c1f52d6945d6f64fec6a2f/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c", size = 769529, upload-time = "2025-09-22T19:50:45.707Z" }, { url = "https://files.pythonhosted.org/packages/99/7c/96d4b5075e30c65ea2064e40c2d657c7c235d7b6ef18751cf89a935b9041/ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl", hash = "sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a", size = 100256, upload-time = "2025-09-22T19:50:48.26Z" }, { url = "https://files.pythonhosted.org/packages/7d/8c/73ee2babd04e8bfcf1fd5c20aa553d18bf0ebc24b592b4f831d12ae46cc0/ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1", size = 118234, upload-time = "2025-09-22T19:50:47.019Z" }, + { url = "https://files.pythonhosted.org/packages/b4/42/ccfb34a25289afbbc42017e4d3d4288e61d35b2e00cfc6b92974a6a1f94b/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27", size = 271775, upload-time = "2025-09-23T14:24:12.771Z" }, { url = "https://files.pythonhosted.org/packages/82/73/e628a92e80197ff6a79ab81ec3fa00d4cc082d58ab78d3337b7ba7043301/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052", size = 138842, upload-time = "2025-09-22T19:50:49.156Z" }, { url = "https://files.pythonhosted.org/packages/2b/c5/346c7094344a60419764b4b1334d9e0285031c961176ff88ffb652405b0c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a", size = 647404, upload-time = "2025-09-22T19:50:52.921Z" }, { url = "https://files.pythonhosted.org/packages/df/99/65080c863eb06d4498de3d6c86f3e90595e02e159fd8529f1565f56cfe2c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29", size = 753141, upload-time = "2025-09-22T19:50:50.294Z" }, { url = "https://files.pythonhosted.org/packages/3d/e3/0de85f3e3333f8e29e4b10244374a202a87665d1131798946ee22cf05c7c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4", size = 703477, upload-time = "2025-09-22T19:50:51.508Z" }, + { url = "https://files.pythonhosted.org/packages/d9/25/0d2f09d8833c7fd77ab8efeff213093c16856479a9d293180a0d89f6bed9/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9", size = 741157, upload-time = "2025-09-23T18:42:50.408Z" }, { url = "https://files.pythonhosted.org/packages/d3/8c/959f10c2e2153cbdab834c46e6954b6dd9e3b109c8f8c0a3cf1618310985/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259", size = 745859, upload-time = "2025-09-22T19:50:54.497Z" }, { url = "https://files.pythonhosted.org/packages/ed/6b/e580a7c18b485e1a5f30a32cda96b20364b0ba649d9d2baaf72f8bd21f83/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023", size = 770200, upload-time = "2025-09-22T19:50:55.718Z" }, { url = "https://files.pythonhosted.org/packages/ef/44/3455eebc761dc8e8fdced90f2b0a3fa61e32ba38b50de4130e2d57db0f21/ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl", hash = "sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54", size = 98829, upload-time = "2025-09-22T19:50:58.895Z" }, { url = "https://files.pythonhosted.org/packages/76/ab/5121f7f3b651db93de546f8c982c241397aad0a4765d793aca1dac5eadee/ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68", size = 115570, upload-time = "2025-09-22T19:50:57.981Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ae/e3811f05415594025e96000349d3400978adaed88d8f98d494352d9761ee/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32", size = 269205, upload-time = "2025-09-23T14:24:15.06Z" }, { url = "https://files.pythonhosted.org/packages/72/06/7d51f4688d6d72bb72fa74254e1593c4f5ebd0036be5b41fe39315b275e9/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85", size = 137417, upload-time = "2025-09-22T19:50:59.82Z" }, { url = "https://files.pythonhosted.org/packages/5a/08/b4499234a420ef42960eeb05585df5cc7eb25ccb8c980490b079e6367050/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e", size = 642558, upload-time = "2025-09-22T19:51:03.388Z" }, { url = "https://files.pythonhosted.org/packages/b6/ba/1975a27dedf1c4c33306ee67c948121be8710b19387aada29e2f139c43ee/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb", size = 744087, upload-time = "2025-09-22T19:51:00.897Z" }, { url = "https://files.pythonhosted.org/packages/20/15/8a19a13d27f3bd09fa18813add8380a29115a47b553845f08802959acbce/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d", size = 699709, upload-time = "2025-09-22T19:51:02.075Z" }, + { url = "https://files.pythonhosted.org/packages/19/ee/8d6146a079ad21e534b5083c9ee4a4c8bec42f79cf87594b60978286b39a/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59", size = 708926, upload-time = "2025-09-23T18:42:51.707Z" }, { url = "https://files.pythonhosted.org/packages/a9/f5/426b714abdc222392e68f3b8ad323930d05a214a27c7e7a0f06c69126401/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca", size = 740202, upload-time = "2025-09-22T19:51:04.673Z" }, { url = "https://files.pythonhosted.org/packages/3d/ac/3c5c2b27a183f4fda8a57c82211721c016bcb689a4a175865f7646db9f94/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6", size = 765196, upload-time = "2025-09-22T19:51:05.916Z" }, { url = "https://files.pythonhosted.org/packages/92/2e/06f56a71fd55021c993ed6e848c9b2e5e9cfce180a42179f0ddd28253f7c/ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl", hash = "sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2", size = 98635, upload-time = "2025-09-22T19:51:08.183Z" }, From 72e411c43d3d2a27d43407f0a4a41b416db256fe Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:40:31 +0000 Subject: [PATCH 33/39] Fix uv lock to mainline --- uv.lock | 191 ++++++++++++++++++++++++++------------------------------ 1 file changed, 90 insertions(+), 101 deletions(-) diff --git a/uv.lock b/uv.lock index d03906d4..d1a67486 100644 --- a/uv.lock +++ b/uv.lock @@ -573,63 +573,55 @@ wheels = [ [[package]] name = "coverage" -version = "7.10.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, - { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, - { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, - { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, - { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, - { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, - { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, - { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, - { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, - { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, - { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, - { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, - { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, - { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, - { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, - { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, - { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, - { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, - { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, - { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, - { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, - { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, - { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, - { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, - { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, - { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, - { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, - { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, - { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, - { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, - { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, - { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, - { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, - { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +version = "7.10.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, + { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, + { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, + { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" }, + { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, + { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" }, + { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, + { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, + { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, + { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, + { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" }, + { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" }, + { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, + { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, + { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" }, + { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, ] [package.optional-dependencies] @@ -2739,11 +2731,11 @@ crypto = [ [[package]] name = "pyparsing" -version = "3.2.5" +version = "3.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/c9/b4594e6a81371dfa9eb7a2c110ad682acf985d96115ae8b25a1d63b4bf3b/pyparsing-3.2.4.tar.gz", hash = "sha256:fff89494f45559d0f2ce46613b419f632bbb6afbdaed49696d322bcf98a58e99", size = 1098809, upload-time = "2025-09-13T05:47:19.732Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl", hash = "sha256:91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36", size = 113869, upload-time = "2025-09-13T05:47:17.863Z" }, ] [[package]] @@ -3131,40 +3123,37 @@ wheels = [ [[package]] name = "ruamel-yaml-clib" -version = "0.2.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/e9/39ec4d4b3f91188fad1842748f67d4e749c77c37e353c4e545052ee8e893/ruamel.yaml.clib-0.2.14.tar.gz", hash = "sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e", size = 225394, upload-time = "2025-09-22T19:51:23.753Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/9f/3c51e9578b8c36fcc4bdd271a1a5bb65963a74a4b6ad1a989768a22f6c2a/ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e", size = 270207, upload-time = "2025-09-23T14:24:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/4a/16/cb02815bc2ae9c66760c0c061d23c7358f9ba51dae95ac85247662b7fbe2/ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d", size = 137780, upload-time = "2025-09-22T19:50:37.734Z" }, - { url = "https://files.pythonhosted.org/packages/31/c6/fc687cd1b93bff8e40861eea46d6dc1a6a778d9a085684e4045ff26a8e40/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9", size = 641590, upload-time = "2025-09-22T19:50:41.978Z" }, - { url = "https://files.pythonhosted.org/packages/45/5d/65a2bc08b709b08576b3f307bf63951ee68a8e047cbbda6f1c9864ecf9a7/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70", size = 738090, upload-time = "2025-09-22T19:50:39.152Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d0/a70a03614d9a6788a3661ab1538879ed2aae4e84d861f101243116308a37/ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98", size = 700744, upload-time = "2025-09-22T19:50:40.811Z" }, - { url = "https://files.pythonhosted.org/packages/77/30/c93fa457611f79946d5cb6cc97493ca5425f3f21891d7b1f9b44eaa1b38e/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee", size = 742321, upload-time = "2025-09-23T18:42:48.916Z" }, - { url = "https://files.pythonhosted.org/packages/40/85/e2c54ad637117cd13244a4649946eaa00f32edcb882d1f92df90e079ab00/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d", size = 743805, upload-time = "2025-09-22T19:50:43.58Z" }, - { url = "https://files.pythonhosted.org/packages/81/50/f899072c38877d8ef5382e0b3d47f8c4346226c1f52d6945d6f64fec6a2f/ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c", size = 769529, upload-time = "2025-09-22T19:50:45.707Z" }, - { url = "https://files.pythonhosted.org/packages/99/7c/96d4b5075e30c65ea2064e40c2d657c7c235d7b6ef18751cf89a935b9041/ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl", hash = "sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a", size = 100256, upload-time = "2025-09-22T19:50:48.26Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8c/73ee2babd04e8bfcf1fd5c20aa553d18bf0ebc24b592b4f831d12ae46cc0/ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1", size = 118234, upload-time = "2025-09-22T19:50:47.019Z" }, - { url = "https://files.pythonhosted.org/packages/b4/42/ccfb34a25289afbbc42017e4d3d4288e61d35b2e00cfc6b92974a6a1f94b/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27", size = 271775, upload-time = "2025-09-23T14:24:12.771Z" }, - { url = "https://files.pythonhosted.org/packages/82/73/e628a92e80197ff6a79ab81ec3fa00d4cc082d58ab78d3337b7ba7043301/ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052", size = 138842, upload-time = "2025-09-22T19:50:49.156Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c5/346c7094344a60419764b4b1334d9e0285031c961176ff88ffb652405b0c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a", size = 647404, upload-time = "2025-09-22T19:50:52.921Z" }, - { url = "https://files.pythonhosted.org/packages/df/99/65080c863eb06d4498de3d6c86f3e90595e02e159fd8529f1565f56cfe2c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29", size = 753141, upload-time = "2025-09-22T19:50:50.294Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e3/0de85f3e3333f8e29e4b10244374a202a87665d1131798946ee22cf05c7c/ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4", size = 703477, upload-time = "2025-09-22T19:50:51.508Z" }, - { url = "https://files.pythonhosted.org/packages/d9/25/0d2f09d8833c7fd77ab8efeff213093c16856479a9d293180a0d89f6bed9/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9", size = 741157, upload-time = "2025-09-23T18:42:50.408Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8c/959f10c2e2153cbdab834c46e6954b6dd9e3b109c8f8c0a3cf1618310985/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259", size = 745859, upload-time = "2025-09-22T19:50:54.497Z" }, - { url = "https://files.pythonhosted.org/packages/ed/6b/e580a7c18b485e1a5f30a32cda96b20364b0ba649d9d2baaf72f8bd21f83/ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023", size = 770200, upload-time = "2025-09-22T19:50:55.718Z" }, - { url = "https://files.pythonhosted.org/packages/ef/44/3455eebc761dc8e8fdced90f2b0a3fa61e32ba38b50de4130e2d57db0f21/ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl", hash = "sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54", size = 98829, upload-time = "2025-09-22T19:50:58.895Z" }, - { url = "https://files.pythonhosted.org/packages/76/ab/5121f7f3b651db93de546f8c982c241397aad0a4765d793aca1dac5eadee/ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68", size = 115570, upload-time = "2025-09-22T19:50:57.981Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ae/e3811f05415594025e96000349d3400978adaed88d8f98d494352d9761ee/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32", size = 269205, upload-time = "2025-09-23T14:24:15.06Z" }, - { url = "https://files.pythonhosted.org/packages/72/06/7d51f4688d6d72bb72fa74254e1593c4f5ebd0036be5b41fe39315b275e9/ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85", size = 137417, upload-time = "2025-09-22T19:50:59.82Z" }, - { url = "https://files.pythonhosted.org/packages/5a/08/b4499234a420ef42960eeb05585df5cc7eb25ccb8c980490b079e6367050/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e", size = 642558, upload-time = "2025-09-22T19:51:03.388Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ba/1975a27dedf1c4c33306ee67c948121be8710b19387aada29e2f139c43ee/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb", size = 744087, upload-time = "2025-09-22T19:51:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/20/15/8a19a13d27f3bd09fa18813add8380a29115a47b553845f08802959acbce/ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d", size = 699709, upload-time = "2025-09-22T19:51:02.075Z" }, - { url = "https://files.pythonhosted.org/packages/19/ee/8d6146a079ad21e534b5083c9ee4a4c8bec42f79cf87594b60978286b39a/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59", size = 708926, upload-time = "2025-09-23T18:42:51.707Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/426b714abdc222392e68f3b8ad323930d05a214a27c7e7a0f06c69126401/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca", size = 740202, upload-time = "2025-09-22T19:51:04.673Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ac/3c5c2b27a183f4fda8a57c82211721c016bcb689a4a175865f7646db9f94/ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6", size = 765196, upload-time = "2025-09-22T19:51:05.916Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/06f56a71fd55021c993ed6e848c9b2e5e9cfce180a42179f0ddd28253f7c/ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl", hash = "sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2", size = 98635, upload-time = "2025-09-22T19:51:08.183Z" }, - { url = "https://files.pythonhosted.org/packages/51/79/76aba16a1689b50528224b182f71097ece338e7a4ab55e84c2e73443b78a/ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78", size = 115238, upload-time = "2025-09-22T19:51:07.081Z" }, +version = "0.2.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, + { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, + { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, + { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, + { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" }, + { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" }, + { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" }, + { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" }, + { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011, upload-time = "2024-10-20T10:13:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488, upload-time = "2024-10-20T10:13:05.906Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066, upload-time = "2024-10-20T10:13:07.26Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785, upload-time = "2024-10-20T10:13:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017, upload-time = "2024-10-21T11:26:48.866Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270, upload-time = "2024-10-21T11:26:50.213Z" }, + { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059, upload-time = "2024-12-11T19:58:18.846Z" }, + { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583, upload-time = "2024-10-20T10:13:09.658Z" }, + { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190, upload-time = "2024-10-20T10:13:10.66Z" }, ] [[package]] @@ -3697,15 +3686,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.36.0" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/5e/f0cd46063a02fd8515f0e880c37d2657845b7306c16ce6c4ffc44afd9036/uvicorn-0.36.0.tar.gz", hash = "sha256:527dc68d77819919d90a6b267be55f0e76704dca829d34aea9480be831a9b9d9", size = 80032, upload-time = "2025-09-20T01:07:14.418Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/06/5cc0542b47c0338c1cb676b348e24a1c29acabc81000bced518231dded6f/uvicorn-0.36.0-py3-none-any.whl", hash = "sha256:6bb4ba67f16024883af8adf13aba3a9919e415358604ce46780d3f9bdc36d731", size = 67675, upload-time = "2025-09-20T01:07:12.984Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] [[package]] @@ -3797,11 +3786,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.14" +version = "0.2.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] [[package]] @@ -4059,4 +4048,4 @@ source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] +] \ No newline at end of file From 55d9464c2818c35f5104bc60220e154203a72ab5 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Wed, 24 Sep 2025 19:42:24 +0000 Subject: [PATCH 34/39] Pre-commit --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index d1a67486..dc0f6c03 100644 --- a/uv.lock +++ b/uv.lock @@ -4048,4 +4048,4 @@ source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] \ No newline at end of file +] From 7b4020eb6b51830f4513ae34128a5dd5f2d28bb8 Mon Sep 17 00:00:00 2001 From: Brian Michell Date: Mon, 29 Sep 2025 17:57:55 -0500 Subject: [PATCH 35/39] Removed raw byte inserts Removed the insertions of raw bytes into the raw bytes Variable. This issue will be addressed in tgsai/segy release >0.5.1 --- src/mdio/converters/segy.py | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/src/mdio/converters/segy.py b/src/mdio/converters/segy.py index d5403b9c..a96761c3 100644 --- a/src/mdio/converters/segy.py +++ b/src/mdio/converters/segy.py @@ -4,15 +4,12 @@ import logging import os -from copy import deepcopy from typing import TYPE_CHECKING import numpy as np import zarr from segy import SegyFile from segy.config import SegySettings -from segy.schema import HeaderField -from segy.schema import ScalarType as ScalarType2 from segy.standards.codes import MeasurementSystem as segy_MeasurementSystem from segy.standards.fields.trace import Rev0 as TraceHeaderFieldsRev0 @@ -344,33 +341,6 @@ def _add_grid_override_to_metadata(dataset: Dataset, grid_overrides: dict[str, A dataset.metadata.attributes["gridOverrides"] = grid_overrides -def _scalar_to_size(scalar: ScalarType2) -> int: - # TODO(BrianMichell): #0000 Lazy way to support conversion. - if scalar == ScalarType2.STRING8: - return 8 - try: - return int(str(scalar)[-2:]) // 8 - except ValueError: - return 1 - - -def _customize_segy_spec(segy_spec: SegySpec) -> SegySpec: - assigned_bytes = [] - - ret = deepcopy(segy_spec) - - for field in segy_spec.trace.header.fields: - byte = field.byte - 1 - for i in range(byte, byte + _scalar_to_size(field.format)): - assigned_bytes.append(i) # noqa: PERF402 - unassigned_bytes = [i for i in range(240) if i not in assigned_bytes] - field_to_customize = [ - HeaderField(name=f"__MDIO_RAW_UNSPECIFIED_Field_{i}", format=ScalarType.UINT8, byte=i + 1) - for i in unassigned_bytes - ] - return ret.customize(trace_header_fields=field_to_customize) - - def _add_raw_headers_to_template(mdio_template: AbstractDatasetTemplate) -> AbstractDatasetTemplate: """Add raw headers capability to the MDIO template by monkey-patching its _add_variables method. @@ -451,9 +421,6 @@ def segy_to_mdio( # noqa PLR0913 input_path = _normalize_path(input_path) output_path = _normalize_path(output_path) - if os.getenv("MDIO__DO_RAW_HEADERS") == "1": - segy_spec = _customize_segy_spec(segy_spec) - if not overwrite and output_path.exists(): err = f"Output location '{output_path.as_posix()}' exists. Set `overwrite=True` if intended." raise FileExistsError(err) From f915e14824d1222ae86e9de8b7e515fde5d2d2fb Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 30 Sep 2025 19:09:24 +0000 Subject: [PATCH 36/39] Use new segy API calls --- pyproject.toml | 2 +- src/mdio/segy/_disaster_recovery_wrapper.py | 23 ++++++++++----------- uv.lock | 14 +++++-------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 479a6220..6631f19d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "psutil>=7.0.0", "pydantic>=2.11.9", "rich>=14.1.0", - "segy>=0.5.0", + "segy @ git+https://github.com/TGSAI/segy.git@easier-access-to-raw-bytes", "tqdm>=4.67.1", "universal-pathlib>=0.2.6", "xarray>=2025.9.1", diff --git a/src/mdio/segy/_disaster_recovery_wrapper.py b/src/mdio/segy/_disaster_recovery_wrapper.py index aaa64773..6d983b56 100644 --- a/src/mdio/segy/_disaster_recovery_wrapper.py +++ b/src/mdio/segy/_disaster_recovery_wrapper.py @@ -2,11 +2,8 @@ from __future__ import annotations -from copy import deepcopy from typing import TYPE_CHECKING -import numpy as np - if TYPE_CHECKING: from numpy.typing import NDArray from segy import SegyFile @@ -16,19 +13,21 @@ class SegyFileTraceDataWrapper: def __init__(self, segy_file: SegyFile, indices: int | list[int] | NDArray | slice): self.segy_file = segy_file self.indices = indices - self._header_pipeline = deepcopy(segy_file.accessors.header_decode_pipeline) - segy_file.accessors.header_decode_pipeline.transforms = [] - self.traces = segy_file.trace[indices] + self.idx = self.segy_file.trace.normalize_and_validate_query(self.indices) + self.traces = self.segy_file.trace.fetch(self.idx, raw=True) + + self.raw_view = self.traces.view(self.segy_file.spec.trace.dtype) + self.decoded_traces = self.segy_file.accessors.trace_decode_pipeline.apply(self.raw_view.copy()) - @property - def header(self) -> NDArray: - # The copy is necessary to avoid applying the pipeline to the original header. - return self._header_pipeline.apply(self.traces.header.copy()) @property def raw_header(self) -> NDArray: - return np.ascontiguousarray(self.traces.header.copy()).view("|V240") + return self.raw_view.header.view("|V240") + + @property + def header(self) -> NDArray: + return self.decoded_traces.header @property def sample(self) -> NDArray: - return self.traces.sample + return self.decoded_traces.sample diff --git a/uv.lock b/uv.lock index dc0f6c03..1b1c6128 100644 --- a/uv.lock +++ b/uv.lock @@ -1922,7 +1922,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=2.11.9" }, { name = "rich", specifier = ">=14.1.0" }, { name = "s3fs", marker = "extra == 'cloud'", specifier = ">=2025.9.0" }, - { name = "segy", specifier = ">=0.5.0" }, + { name = "segy", git = "https://github.com/TGSAI/segy.git?rev=easier-access-to-raw-bytes" }, { name = "tqdm", specifier = ">=4.67.1" }, { name = "universal-pathlib", specifier = ">=0.2.6" }, { name = "xarray", specifier = ">=2025.9.1" }, @@ -3199,7 +3199,7 @@ wheels = [ [[package]] name = "segy" version = "0.5.0.post1" -source = { registry = "https://pypi.org/simple" } +source = { git = "https://github.com/TGSAI/segy.git?rev=easier-access-to-raw-bytes#9db5edfca31861fe211131a6732b274e551a0588" } dependencies = [ { name = "fsspec" }, { name = "numba" }, @@ -3210,10 +3210,6 @@ dependencies = [ { name = "rapidfuzz" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/c2/aae81f9f9ae43c28c2d6b543719e6f1805d50d9565f3616af9ce29e3fbc0/segy-0.5.0.post1.tar.gz", hash = "sha256:b8c140fb10cfd4807bc6aab46a6f09d98b82c4995e045f568be3bbf6c044aba6", size = 43037, upload-time = "2025-09-15T13:33:42.348Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/f0/b67a8a89dbb331d55e9b37c779c270a48ff09ca83a0055a65a84f33dc100/segy-0.5.0.post1-py3-none-any.whl", hash = "sha256:158661da578147fa5cfbcf335047a2459f86aa5522e1acc4249bb8252d26be55", size = 55408, upload-time = "2025-09-15T13:33:40.571Z" }, -] [[package]] name = "setuptools" @@ -3611,7 +3607,7 @@ wheels = [ [[package]] name = "typer" -version = "0.16.1" +version = "0.19.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -3619,9 +3615,9 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/78/d90f616bf5f88f8710ad067c1f8705bf7618059836ca084e5bb2a0855d75/typer-0.16.1.tar.gz", hash = "sha256:d358c65a464a7a90f338e3bb7ff0c74ac081449e53884b12ba658cbd72990614", size = 102836, upload-time = "2025-08-18T19:18:22.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/76/06dbe78f39b2203d2a47d5facc5df5102d0561e2807396471b5f7c5a30a1/typer-0.16.1-py3-none-any.whl", hash = "sha256:90ee01cb02d9b8395ae21ee3368421faf21fa138cb2a541ed369c08cec5237c9", size = 46397, upload-time = "2025-08-18T19:18:21.663Z" }, + { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, ] [[package]] From 937317f326025650820397858c9bfa1a504ef2fb Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 30 Sep 2025 20:35:25 +0000 Subject: [PATCH 37/39] Updates to get working --- pyproject.toml | 2 +- tests/unit/test_disaster_recovery_wrapper.py | 35 -------------------- uv.lock | 4 +-- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6631f19d..0638058a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "psutil>=7.0.0", "pydantic>=2.11.9", "rich>=14.1.0", - "segy @ git+https://github.com/TGSAI/segy.git@easier-access-to-raw-bytes", + "segy @ git+https://github.com/TGSAI/segy.git@fe82079fb463c0923b47227fe38bdc8b6c1ecee5", "tqdm>=4.67.1", "universal-pathlib>=0.2.6", "xarray>=2025.9.1", diff --git a/tests/unit/test_disaster_recovery_wrapper.py b/tests/unit/test_disaster_recovery_wrapper.py index bb1864f6..376f1dea 100644 --- a/tests/unit/test_disaster_recovery_wrapper.py +++ b/tests/unit/test_disaster_recovery_wrapper.py @@ -288,38 +288,3 @@ def test_different_index_types( assert wrapper.header.size == expected_count - def test_header_pipeline_preservation(self, temp_dir: Path, basic_segy_spec: SegySpec, segy_config: dict) -> None: - """Test that the wrapper preserves the original header pipeline.""" - config_name = segy_config["name"] - endianness = segy_config["endianness"] - data_format = segy_config["data_format"] - - segy_path = temp_dir / f"test_pipeline_{config_name}.segy" - - # Create test SEGY file - num_traces = 5 - samples_per_trace = SAMPLES_PER_TRACE - - spec = self.create_test_segy_file( - spec=basic_segy_spec, - num_traces=num_traces, - samples_per_trace=samples_per_trace, - output_path=segy_path, - endianness=endianness, - data_format=data_format, - ) - - # Load the SEGY file - segy_file = SegyFile(segy_path, spec=spec) - - # Store original pipeline transforms count - original_transforms_count = len(segy_file.accessors.header_decode_pipeline.transforms) - - # Create wrapper - wrapper = SegyFileTraceDataWrapper(segy_file, 0) - - # Verify that the original SEGY file's pipeline was modified (transforms cleared) - assert len(segy_file.accessors.header_decode_pipeline.transforms) == 0 - - # Verify that the wrapper has its own pipeline with the original transforms - assert len(wrapper._header_pipeline.transforms) == original_transforms_count diff --git a/uv.lock b/uv.lock index 1b1c6128..d5e3dfae 100644 --- a/uv.lock +++ b/uv.lock @@ -1922,7 +1922,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=2.11.9" }, { name = "rich", specifier = ">=14.1.0" }, { name = "s3fs", marker = "extra == 'cloud'", specifier = ">=2025.9.0" }, - { name = "segy", git = "https://github.com/TGSAI/segy.git?rev=easier-access-to-raw-bytes" }, + { name = "segy", git = "https://github.com/TGSAI/segy.git?rev=fe82079fb463c0923b47227fe38bdc8b6c1ecee5" }, { name = "tqdm", specifier = ">=4.67.1" }, { name = "universal-pathlib", specifier = ">=0.2.6" }, { name = "xarray", specifier = ">=2025.9.1" }, @@ -3199,7 +3199,7 @@ wheels = [ [[package]] name = "segy" version = "0.5.0.post1" -source = { git = "https://github.com/TGSAI/segy.git?rev=easier-access-to-raw-bytes#9db5edfca31861fe211131a6732b274e551a0588" } +source = { git = "https://github.com/TGSAI/segy.git?rev=fe82079fb463c0923b47227fe38bdc8b6c1ecee5#fe82079fb463c0923b47227fe38bdc8b6c1ecee5" } dependencies = [ { name = "fsspec" }, { name = "numba" }, From 3862e1f534610310443d9c88bc69c54178cf1924 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 30 Sep 2025 20:48:34 +0000 Subject: [PATCH 38/39] Use released version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0638058a..7d5a7e0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "psutil>=7.0.0", "pydantic>=2.11.9", "rich>=14.1.0", - "segy @ git+https://github.com/TGSAI/segy.git@fe82079fb463c0923b47227fe38bdc8b6c1ecee5", + "segy>=0.5.1.post1", "tqdm>=4.67.1", "universal-pathlib>=0.2.6", "xarray>=2025.9.1", From 28f645cdc24b32a76d9f3e2fe4d78cc7d62fbe36 Mon Sep 17 00:00:00 2001 From: BrianMichell Date: Tue, 30 Sep 2025 20:50:00 +0000 Subject: [PATCH 39/39] Linting --- src/mdio/segy/_disaster_recovery_wrapper.py | 1 - tests/unit/test_disaster_recovery_wrapper.py | 1 - uv.lock | 10 +++++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/mdio/segy/_disaster_recovery_wrapper.py b/src/mdio/segy/_disaster_recovery_wrapper.py index 6d983b56..882bbdd8 100644 --- a/src/mdio/segy/_disaster_recovery_wrapper.py +++ b/src/mdio/segy/_disaster_recovery_wrapper.py @@ -19,7 +19,6 @@ def __init__(self, segy_file: SegyFile, indices: int | list[int] | NDArray | sli self.raw_view = self.traces.view(self.segy_file.spec.trace.dtype) self.decoded_traces = self.segy_file.accessors.trace_decode_pipeline.apply(self.raw_view.copy()) - @property def raw_header(self) -> NDArray: return self.raw_view.header.view("|V240") diff --git a/tests/unit/test_disaster_recovery_wrapper.py b/tests/unit/test_disaster_recovery_wrapper.py index 376f1dea..4edee675 100644 --- a/tests/unit/test_disaster_recovery_wrapper.py +++ b/tests/unit/test_disaster_recovery_wrapper.py @@ -287,4 +287,3 @@ def test_different_index_types( expected_count = 1 assert wrapper.header.size == expected_count - diff --git a/uv.lock b/uv.lock index d5e3dfae..f73fa0d2 100644 --- a/uv.lock +++ b/uv.lock @@ -1922,7 +1922,7 @@ requires-dist = [ { name = "pydantic", specifier = ">=2.11.9" }, { name = "rich", specifier = ">=14.1.0" }, { name = "s3fs", marker = "extra == 'cloud'", specifier = ">=2025.9.0" }, - { name = "segy", git = "https://github.com/TGSAI/segy.git?rev=fe82079fb463c0923b47227fe38bdc8b6c1ecee5" }, + { name = "segy", specifier = ">=0.5.1.post1" }, { name = "tqdm", specifier = ">=4.67.1" }, { name = "universal-pathlib", specifier = ">=0.2.6" }, { name = "xarray", specifier = ">=2025.9.1" }, @@ -3198,8 +3198,8 @@ wheels = [ [[package]] name = "segy" -version = "0.5.0.post1" -source = { git = "https://github.com/TGSAI/segy.git?rev=fe82079fb463c0923b47227fe38bdc8b6c1ecee5#fe82079fb463c0923b47227fe38bdc8b6c1ecee5" } +version = "0.5.1.post1" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fsspec" }, { name = "numba" }, @@ -3210,6 +3210,10 @@ dependencies = [ { name = "rapidfuzz" }, { name = "typer" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c5/c71d4f52eb1587bdeb8401445ac65b08603fb6f77ada46933dec5fbbd6f8/segy-0.5.1.post1.tar.gz", hash = "sha256:655d1b26aa7a698084d190c8b5c7d12802cfbc9627067614606b1d69c5f0f4ae", size = 43354, upload-time = "2025-09-30T20:35:19.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/ff/ee1b5c982ddfb7185fac41b85ce7a8bd2d5604d6129183a63c2a851109d3/segy-0.5.1.post1-py3-none-any.whl", hash = "sha256:6f36a0795c459d77a3d715d7e5b1444be4cb8368720f89111d452be93d1cf7f1", size = 55757, upload-time = "2025-09-30T20:35:18.665Z" }, +] [[package]] name = "setuptools"