|
6 | 6 | from typing import TYPE_CHECKING |
7 | 7 |
|
8 | 8 | import numpy as np |
| 9 | +from segy import SegyFile |
9 | 10 | from segy.arrays import HeaderArray |
| 11 | +from zarr import open_group as zarr_open_group |
10 | 12 |
|
11 | 13 | from mdio.core.config import MDIOSettings |
12 | 14 | from mdio.segy._raw_trace_wrapper import SegyFileRawTraceWrapper |
13 | 15 | from mdio.segy.file import SegyFileArguments |
14 | 16 | from mdio.segy.file import SegyFileWrapper |
15 | 17 |
|
16 | 18 | if TYPE_CHECKING: |
17 | | - from segy import SegyFile |
18 | 19 | from zarr import Array as zarr_Array |
19 | 20 |
|
20 | 21 | from zarr.core.config import config as zarr_config |
@@ -68,30 +69,70 @@ def header_scan_worker( |
68 | 69 | return HeaderArray(trace_header) # wrap back so we can use aliases |
69 | 70 |
|
70 | 71 |
|
71 | | -def trace_worker( # noqa: PLR0913 |
72 | | - segy_file: SegyFile, |
73 | | - data_array: zarr_Array, |
74 | | - header_array: zarr_Array | None, |
75 | | - raw_header_array: zarr_Array | None, |
76 | | - region: dict[str, slice], |
| 72 | +# Per-worker process state populated once by `trace_worker_init`. Keeping the SEG-Y handle, |
| 73 | +# Zarr array handles, and the (compressed, in-memory) grid map here lets us pickle them a single |
| 74 | +# time per worker via the pool initializer instead of once per submitted block. The grid map is |
| 75 | +# retained as a compressed in-memory Zarr array and sliced lazily per region, so each worker only |
| 76 | +# materializes its own block rather than the full dense map. |
| 77 | +_worker_state: dict[str, object] = {} |
| 78 | + |
| 79 | + |
| 80 | +def trace_worker_init( # noqa: PLR0913 |
| 81 | + segy_file_kwargs: SegyFileArguments, |
| 82 | + output_path: str, |
| 83 | + storage_options: dict[str, object] | None, |
| 84 | + use_consolidated: bool, |
| 85 | + data_variable_name: str, |
77 | 86 | grid_map: zarr_Array, |
78 | | -) -> SummaryStatistics | None: |
| 87 | +) -> None: |
| 88 | + """Initialize per-process state for trace ingestion workers. |
| 89 | +
|
| 90 | + Used as the `ProcessPoolExecutor` initializer so the SEG-Y file, Zarr output handles, and grid |
| 91 | + map are opened/transferred once per worker process rather than re-pickled for every block. |
| 92 | +
|
| 93 | + Args: |
| 94 | + segy_file_kwargs: Arguments to open the SegyFile instance. |
| 95 | + output_path: POSIX path to the output MDIO Zarr store. |
| 96 | + storage_options: fsspec storage options for the output store. |
| 97 | + use_consolidated: Whether to open the group with consolidated metadata (Zarr V2). |
| 98 | + data_variable_name: Name of the data variable in the dataset. |
| 99 | + grid_map: Compressed in-memory Zarr array mapping live traces to their positions. |
| 100 | + """ |
| 101 | + # Setting the zarr config to 1 thread to ensure we honor the `MDIO__IMPORT__CPU_COUNT` environment variable. |
| 102 | + # The Zarr 3 engine utilizes multiple threads. This can lead to resource contention and unpredictable memory usage. |
| 103 | + zarr_config.set({"threading.max_workers": 1}) |
| 104 | + |
| 105 | + zarr_group = zarr_open_group( |
| 106 | + output_path, |
| 107 | + mode="r+", |
| 108 | + storage_options=storage_options, |
| 109 | + use_consolidated=use_consolidated, |
| 110 | + ) |
| 111 | + |
| 112 | + _worker_state["segy_file"] = SegyFile(**segy_file_kwargs) |
| 113 | + _worker_state["data_array"] = zarr_group[data_variable_name] |
| 114 | + _worker_state["header_array"] = zarr_group.get("headers") |
| 115 | + _worker_state["raw_header_array"] = zarr_group.get("raw_headers") |
| 116 | + _worker_state["grid_map"] = grid_map |
| 117 | + |
| 118 | + |
| 119 | +def trace_worker(region: dict[str, slice]) -> SummaryStatistics | None: |
79 | 120 | """Writes a subset of traces from a region of the dataset of Zarr file. |
80 | 121 |
|
| 122 | + Reads its shared inputs (SEG-Y handle, Zarr arrays, grid map) from the per-process state set up |
| 123 | + by `trace_worker_init`, so only the lightweight `region` is pickled per block. |
| 124 | +
|
81 | 125 | Args: |
82 | | - segy_file: The opened SEG-Y file. |
83 | | - data_array: Zarr array for writing trace data. |
84 | | - header_array: Zarr array for writing trace headers (or None if not needed). |
85 | | - raw_header_array: Zarr array for writing raw headers (or None if not needed). |
86 | 126 | region: Region of the dataset to write to. |
87 | | - grid_map: Zarr array mapping live traces to their positions in the dataset. |
88 | 127 |
|
89 | 128 | Returns: |
90 | 129 | SummaryStatistics object containing statistics about the written traces. |
91 | 130 | """ |
92 | | - # Setting the zarr config to 1 thread to ensure we honor the `MDIO__IMPORT__CPU_COUNT` environment variable. |
93 | | - # The Zarr 3 engine utilizes multiple threads. This can lead to resource contention and unpredictable memory usage. |
94 | | - zarr_config.set({"threading.max_workers": 1}) |
| 131 | + segy_file: SegyFile = _worker_state["segy_file"] |
| 132 | + data_array: zarr_Array = _worker_state["data_array"] |
| 133 | + header_array: zarr_Array | None = _worker_state["header_array"] |
| 134 | + raw_header_array: zarr_Array | None = _worker_state["raw_header_array"] |
| 135 | + grid_map: zarr_Array = _worker_state["grid_map"] |
95 | 136 |
|
96 | 137 | region_slices = tuple(region.values()) |
97 | 138 | local_grid_map = grid_map[region_slices[:-1]] # minus last (vertical) axis |
|
0 commit comments