subsurfacegen/field-scale-dataset-preview
Field-Scale Dataset โ Preview Sample A curated single-family preview of the subsurfacegen/field-scale-dataset full release. Designed for reviewers to download in under 5 minutes and inspect the dataset's content + structure without pulling the full 12 TB. ๐ Quick visual tour All five figures from this preview are pre-rendered below (and again, fully interactive, in view_preview.ipynb). The notebook is rendered inline by HuggingFace with embedded plot outputs, soโฆ See the full description on the dataset page: https://huggingface.co/datasets/subsurfacegen/field-scale-dataset-preview.
0111
1#!/usr/bin/env python2"""Field-Scale Dataset Preview โ NeurIPS-ready viewer for the 4-file sample.3 4Renders five publication-quality figures matching the manuscript's intro5figure style. Pure NumPy + Matplotlib + h5py + hdf5plugin.6 7Usage:8 9 pip install huggingface_hub h5py hdf5plugin numpy matplotlib scipy10 huggingface-cli download subsurfacegen/field-scale-dataset-preview \\11 --repo-type=dataset --local-dir=./preview_data12 python view_preview.py --plot all --input-dir ./preview_data --output-dir ./figs13 14Individual:15 python view_preview.py --plot cube # 3D cutaway render of the volume16 python view_preview.py --plot orthogonal # 3 orthogonal slices w/ crosshairs17 python view_preview.py --plot slice # 2D slice w/ source + streamer18 python view_preview.py --plot wavefield # Wavefield panels w/ velocity bg19 python view_preview.py --plot gather # Shot gather panels (Left/Middle/Right)20"""21from __future__ import annotations22 23import argparse24import sys25from pathlib import Path26 27# Optional: register HDF5 compression filters used by wavefield + gather files.28try:29 import hdf5plugin # noqa: F40130except ImportError:31 print("WARNING: hdf5plugin not installed; wavefield + gather reads may fail. "32 "Install via: pip install hdf5plugin", flush=True)33 34import h5py35import matplotlib36 37matplotlib.use("Agg")38import matplotlib.colors as mcolors39import matplotlib.pyplot as plt40import numpy as np41from mpl_toolkits.mplot3d import Axes3D # noqa: F401 (registers projection)42 43 44# ---------------- Paper-figure constants ----------------45 46# Match render_shot_gather_clean / render_wavefield_panel_clean conventions.47GATHER_SOURCE_INDICES = (8, 32, 56) # paper: SG_SOURCE_INDICES48GATHER_POSITION_NAMES = ("Left", "Middle", "Right")49GATHER_SOURCE_COLORS = ("#B87100", "#004A77", "#A00000") # bronze/navy/burgundy50 51# Wavefield time panels (more than the paper's 3 to show fuller progression)52WAVEFIELD_FRAMES = [53 ("0.20 s", 14), # Start (paper)54 ("1.00 s", 71),55 ("1.96 s", 140), # Middle (paper)56 ("3.00 s", 214),57 ("4.00 s", 285),58 ("5.00 s", 357), # End (paper)59]60 61# 3D orthogonal-slices configuration: which inline + crossline + depth slice62INLINE_INDEX = 507 # the inline that the 2D slice was extracted at63CROSSLINE_INDEX = 500 # mid-y by convention64DEPTH_INDEX_FRAC = 0.5 # mid-depth65 66# Velocity colormap (paper convention: TwoSlopeNorm + seismic)67VEL_VMIN, VEL_VCENTER, VEL_VMAX = 1500.0, 2300.0, 4500.068 69# 3D-cube cutaway render config (matches cutaway_3d_d619_batch.py)70CUBE_VEL_VCENTER = 3100.071CUBE_DEPTH_RANGE = (80, 619)72CUBE_CROSSLINE_RANGE = (150, 1000)73CUBE_INLINE_RANGE = (150, 1000)74CUBE_VIEW_ELEV = 2075CUBE_VIEW_AZIM = -11576CUBE_MAX_FACE_DIM = 35077DX_KM = 0.01078 79# NeurIPS-ready typography (apply globally so every figure is consistent)80plt.rcParams.update({81 "font.size": 14,82 "axes.titlesize": 16,83 "axes.labelsize": 14,84 "xtick.labelsize": 12,85 "ytick.labelsize": 12,86 "legend.fontsize": 12,87 "figure.titlesize": 18,88 "axes.titleweight": "bold",89 "figure.titleweight": "bold",90 "axes.linewidth": 1.4,91 "xtick.major.width": 1.2,92 "ytick.major.width": 1.2,93 "xtick.major.size": 5,94 "ytick.major.size": 5,95 "lines.linewidth": 1.6,96 "savefig.bbox": "tight",97 "savefig.dpi": 150,98})99 100 101# ---------------- helpers ----------------102 103def _vel_norm() -> mcolors.TwoSlopeNorm:104 return mcolors.TwoSlopeNorm(vmin=VEL_VMIN, vcenter=VEL_VCENTER, vmax=VEL_VMAX)105 106 107def _wf_alpha_cmap() -> mcolors.ListedColormap:108 """Seismic with alpha proportional to |v|; tiny values become transparent."""109 base = matplotlib.colormaps["seismic"](np.linspace(0, 1, 256))110 t = np.linspace(-1.0, 1.0, 256)111 alphas = np.clip(np.abs(t) * 1.6, 0.0, 1.0)112 alphas[np.abs(t) < 0.04] = 0.0113 base[:, 3] = alphas114 return mcolors.ListedColormap(base, name="seismic_alpha")115 116 117def _load_h5_dataset(path: Path, key: str) -> tuple[np.ndarray, dict]:118 with h5py.File(path, "r") as f:119 ds = f[key]120 data = ds[...]121 attrs = {}122 for k, v in ds.attrs.items():123 attrs[k] = v.decode() if isinstance(v, bytes) else v124 for k, v in f.attrs.items():125 attrs[f"root.{k}"] = v.decode() if isinstance(v, bytes) else v126 return data, attrs127 128 129def _save(fig, output_dir: Path, name: str):130 output_dir.mkdir(parents=True, exist_ok=True)131 out = output_dir / name132 fig.savefig(out, dpi=150)133 plt.close(fig)134 print(f" wrote {out}")135 136 137def _block_mean(arr: np.ndarray, max_dim: int) -> np.ndarray:138 h, w = arr.shape139 bh = max(1, int(np.ceil(h / max_dim)))140 bw = max(1, int(np.ceil(w / max_dim)))141 if bh == 1 and bw == 1:142 return arr.astype(np.float32, copy=False)143 h2 = (h // bh) * bh144 w2 = (w // bw) * bw145 return (arr[:h2, :w2].astype(np.float32, copy=False)146 .reshape(h2 // bh, bh, w2 // bw, bw).mean(axis=(1, 3)))147 148 149# ---------------- 3D cube cutaway (paper render_cube style) ----------------150 151def plot_3d_cube_cutaway(input_dir: Path, output_dir: Path):152 p = input_dir / "models/gom_d619/gom_151_sos.h5"153 print(f"Loading 3D volume for cutaway: {p}")154 with h5py.File(p, "r") as fh:155 vol = np.asarray(fh["velocity"][...], dtype=np.float32)156 nz, ny, nx = vol.shape157 print(f" shape (nz, ny, nx) = ({nz}, {ny}, {nx})")158 norm = mcolors.TwoSlopeNorm(vmin=VEL_VMIN, vcenter=CUBE_VEL_VCENTER, vmax=VEL_VMAX)159 cmap = plt.get_cmap("seismic")160 161 # Take the cutaway sub-volume (top + 2 sides shaved per paper config)162 z0, z1 = CUBE_DEPTH_RANGE163 y0, y1 = CUBE_CROSSLINE_RANGE164 x0, x1 = CUBE_INLINE_RANGE165 raw_faces = {166 "top": vol[z0, y0:y1, x0:x1],167 "bottom": vol[z1 - 1, y0:y1, x0:x1],168 "left": vol[z0:z1, y0:y1, x0],169 "right": vol[z0:z1, y0:y1, x1 - 1],170 "front": vol[z0:z1, y0, x0:x1],171 "back": vol[z0:z1, y1 - 1, x0:x1],172 }173 faces = {k: _block_mean(v, CUBE_MAX_FACE_DIM) for k, v in raw_faces.items()}174 del vol175 176 x_km_lim = (x0 * DX_KM, (x1 - 1) * DX_KM)177 y_km_lim = (y0 * DX_KM, (y1 - 1) * DX_KM)178 z_km_lim = (z0 * DX_KM, (z1 - 1) * DX_KM)179 180 def coords(face_shape, a_lim, b_lim):181 nh, nw = face_shape182 aa = np.linspace(a_lim[0], a_lim[1], nh)183 bb = np.linspace(b_lim[0], b_lim[1], nw)184 A, B = np.meshgrid(aa, bb, indexing="ij")185 return A, B186 187 fig = plt.figure(figsize=(10, 9))188 ax = fig.add_subplot(111, projection="3d")189 ax.set_facecolor("white")190 surf_kw = dict(shade=False, rstride=1, cstride=1, linewidth=0,191 antialiased=False, rasterized=True)192 193 Y, X = coords(faces["top"].shape, y_km_lim, x_km_lim)194 ax.plot_surface(X, Y, np.full_like(X, z_km_lim[0]),195 facecolors=cmap(norm(faces["top"])), **surf_kw)196 Y, X = coords(faces["bottom"].shape, y_km_lim, x_km_lim)197 ax.plot_surface(X, Y, np.full_like(X, z_km_lim[1]),198 facecolors=cmap(norm(faces["bottom"])), **surf_kw)199 Z2, Y2 = coords(faces["left"].shape, z_km_lim, y_km_lim)200 ax.plot_surface(np.full_like(Y2, x_km_lim[0]), Y2, Z2,201 facecolors=cmap(norm(faces["left"])), **surf_kw)202 Z2, Y2 = coords(faces["right"].shape, z_km_lim, y_km_lim)203 ax.plot_surface(np.full_like(Y2, x_km_lim[1]), Y2, Z2,204 facecolors=cmap(norm(faces["right"])), **surf_kw)205 Z3, X3 = coords(faces["front"].shape, z_km_lim, x_km_lim)206 ax.plot_surface(X3, np.full_like(X3, y_km_lim[0]), Z3,207 facecolors=cmap(norm(faces["front"])), **surf_kw)208 Z3, X3 = coords(faces["back"].shape, z_km_lim, x_km_lim)209 ax.plot_surface(X3, np.full_like(X3, y_km_lim[1]), Z3,210 facecolors=cmap(norm(faces["back"])), **surf_kw)211 212 ax.set_xlim(*x_km_lim); ax.set_ylim(*y_km_lim); ax.set_zlim(*z_km_lim)213 ax.invert_zaxis()214 ax.set_box_aspect((1, 1, 1))215 ax.view_init(elev=CUBE_VIEW_ELEV, azim=CUBE_VIEW_AZIM)216 ax.set_xticks([]); ax.set_yticks([]); ax.set_zticks([])217 for pane in (ax.xaxis.pane, ax.yaxis.pane, ax.zaxis.pane):218 pane.set_facecolor((1, 1, 1, 0)); pane.set_edgecolor((1, 1, 1, 0))219 ax.set_title("3D SOS-smoothed velocity volume (cutaway)\n"220 "gom_151_sos.h5 โ Gulf of Mexico realization",221 pad=18)222 # Manual colorbar223 sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)224 sm.set_array([])225 cbar = fig.colorbar(sm, ax=ax, shrink=0.55, pad=0.05, aspect=20)226 cbar.set_label("velocity (m/s)", fontsize=14)227 cbar.ax.tick_params(labelsize=12)228 _save(fig, output_dir, "model_3d_cube_cutaway.png")229 230 231# ---------------- 3D orthogonal slices with crosshairs ----------------232 233def plot_3d_orthogonal_slices(input_dir: Path, output_dir: Path):234 p = input_dir / "models/gom_d619/gom_151_sos.h5"235 print(f"Loading 3D volume: {p}")236 vol, _ = _load_h5_dataset(p, "velocity")237 nz, ny, nx = vol.shape238 mid_z = int(round(nz * DEPTH_INDEX_FRAC))239 print(f" shape (nz, ny, nx) = ({nz}, {ny}, {nx})")240 print(f" inline_idx={INLINE_INDEX}, crossline_idx={CROSSLINE_INDEX}, depth_idx={mid_z}")241 242 norm = _vel_norm()243 crosshair_kw = dict(color="red", linestyle="--", linewidth=2.0,244 alpha=0.95, dashes=(4, 3))245 246 fig, axes = plt.subplots(1, 3, figsize=(20, 6.5),247 gridspec_kw={"wspace": 0.25})248 249 # Panel 1: inline slice (depth ร x). Crossline + depth slices appear as crosshair.250 inline = vol[:, INLINE_INDEX, :]251 im = axes[0].imshow(inline, aspect="auto", cmap="seismic", norm=norm,252 extent=[0, nx, nz, 0], interpolation="lanczos")253 axes[0].set_title(f"Inline {INLINE_INDEX}\n(the 2D slice file)")254 axes[0].set_xlabel("crossline x (sample)")255 axes[0].set_ylabel("depth (sample)")256 axes[0].axvline(CROSSLINE_INDEX, **crosshair_kw) # where crossline panel cuts257 axes[0].axhline(mid_z, **crosshair_kw) # where depth panel cuts258 259 # Panel 2: crossline slice (depth ร y). Inline + depth slices as crosshair.260 cross = vol[:, :, CROSSLINE_INDEX]261 axes[1].imshow(cross, aspect="auto", cmap="seismic", norm=norm,262 extent=[0, ny, nz, 0], interpolation="lanczos")263 axes[1].set_title(f"Crossline at x={CROSSLINE_INDEX}")264 axes[1].set_xlabel("inline y (sample)")265 axes[1].set_ylabel("depth (sample)")266 axes[1].axvline(INLINE_INDEX, **crosshair_kw) # where inline panel cuts267 axes[1].axhline(mid_z, **crosshair_kw) # where depth panel cuts268 269 # Panel 3: depth slice (y ร x). Inline + crossline cuts as crosshair.270 depth = vol[mid_z, :, :]271 axes[2].imshow(depth, aspect="auto", cmap="seismic", norm=norm,272 extent=[0, nx, ny, 0], interpolation="lanczos")273 axes[2].set_title(f"Depth slice at z={mid_z}")274 axes[2].set_xlabel("crossline x (sample)")275 axes[2].set_ylabel("inline y (sample)")276 axes[2].axhline(INLINE_INDEX, **crosshair_kw) # where inline panel cuts277 axes[2].axvline(CROSSLINE_INDEX, **crosshair_kw) # where crossline panel cuts278 279 # Colorbar with key above it explaining the dashed lines.280 cbar_ax = fig.add_axes([0.92, 0.13, 0.013, 0.66])281 cbar = fig.colorbar(im, cax=cbar_ax)282 cbar.set_label("velocity (m/s)", fontsize=14)283 cbar.ax.tick_params(labelsize=12)284 # Compact key just above the colorbar285 fig.text(0.926, 0.85,286 "โโ red dashed:\nlocations of\nother 2 panels",287 fontsize=11, color="red", weight="bold",288 ha="left", va="bottom")289 290 fig.suptitle("3D SOS volume โ orthogonal slices through gom_151_sos.h5",291 y=0.98)292 _save(fig, output_dir, "model_3d_orthogonal_slices.png")293 294 295# ---------------- 2D slice with source + streamer geometry ----------------296 297def plot_2d_slice(input_dir: Path, output_dir: Path):298 """2D velocity slice with seismic cmap, streamer + source geometry overlay."""299 p = input_dir / "slices/slice_gom_151_il_0507.h5"300 print(f"Loading 2D slice: {p}")301 sl, _ = _load_h5_dataset(p, "velocity")302 nz, nx = sl.shape303 print(f" shape (nz, nx) = ({nz}, {nx})")304 305 # Physical extent (10 m grid spacing)306 extent = (0.0, nx * 0.01, nz * 0.01, 0.0)307 norm = _vel_norm()308 309 fig, ax = plt.subplots(1, 1, figsize=(13, 5.8))310 im = ax.imshow(sl, cmap="seismic", norm=norm,311 extent=extent, aspect="auto", interpolation="lanczos")312 ax.set_xlabel("horizontal x (km)")313 ax.set_ylabel("depth z (km)")314 ax.set_title("2D velocity slice โ slice_gom_151_il_0507.h5 (inline 507 of gom_151)")315 316 # Streamer (1000 receivers @ 10 m depth, 10 m horizontal spacing,317 # standard 0.5 km edge margin per the simulation config).318 rec_z_km = 0.01319 rec_x_km = np.linspace(0.6, 9.4, 1000)320 rec_x_show = rec_x_km[::25]321 ax.scatter(rec_x_show, np.full_like(rec_x_show, rec_z_km),322 marker="v", s=24, c="black", edgecolors="white",323 linewidths=0.4, zorder=10, label="receivers (every 25th)")324 325 # Source for the wavefield panel (paper figure)326 ax.scatter([4.958], [rec_z_km], marker="*", s=460,327 c="gold", edgecolors="black", linewidths=1.2,328 zorder=11, label="wavefield source @ x=4.958 km")329 ax.legend(loc="upper right", framealpha=0.92)330 cbar = fig.colorbar(im, ax=ax, pad=0.01, shrink=0.95)331 cbar.set_label("velocity (m/s)")332 _save(fig, output_dir, "slice_2d_velocity.png")333 334 335# ---------------- Wavefield panels (paper render style) ----------------336 337def plot_wavefield(input_dir: Path, output_dir: Path,338 frames: list[tuple[str, int]] = WAVEFIELD_FRAMES):339 """Wavefield time progression with the velocity model drawn behind."""340 p = input_dir / "wavefields/5s/3-6Hz/wavefield_gom_151_il_0507_srchorizontal4.958km.h5"341 print(f"Loading wavefield: {p}")342 wf, _ = _load_h5_dataset(p, "wavefield")343 print(f" wavefield shape (nt, _, _) = {wf.shape}")344 # Background velocity slice (loaded for the underlay)345 vp = input_dir / "slices/slice_gom_151_il_0507.h5"346 vel, _ = _load_h5_dataset(vp, "velocity")347 print(f" velocity background shape (nz, nx) = {vel.shape}")348 349 extent = (0.0, 10.0, 6.19, 0.0)350 src_x_km = 4.958351 wf_cmap = _wf_alpha_cmap()352 353 n = len(frames)354 ncols = 3355 nrows = int(np.ceil(n / ncols))356 fig, axes = plt.subplots(nrows, ncols, figsize=(6.5 * ncols, 4.8 * nrows),357 gridspec_kw={"wspace": 0.18, "hspace": 0.30})358 axes = np.atleast_2d(axes).flatten()359 360 for idx, (label, frame_idx) in enumerate(frames):361 ax = axes[idx]362 frame_idx = max(0, min(frame_idx, wf.shape[0] - 1))363 # Wavefield is stored (nt, nx, nz) where nx=1000, nz=619 (devito364 # convention). Transpose to (nz, nx) so it aligns with the velocity365 # slice (nz=619, nx=1000) when overlaid on the same physical extent.366 wf_frame = wf[frame_idx].T367 # Underlay velocity model in gray, alpha 0.40368 ax.imshow(vel, cmap="gray", alpha=0.40, extent=extent,369 aspect="auto", interpolation="nearest",370 vmin=float(vel.min()), vmax=float(vel.max()))371 # Wavefield with seismic-alpha cmap (transparent near 0)372 clip = float(np.percentile(np.abs(wf_frame), 99.5))373 if clip <= 0:374 clip = float(np.abs(wf_frame).max() + 1e-12)375 ax.imshow(wf_frame, cmap=wf_cmap,376 norm=mcolors.Normalize(-clip, clip),377 extent=extent, aspect="auto", interpolation="nearest")378 # Source marker379 ax.scatter([src_x_km], [0.0], marker="*", s=320, c="gold",380 edgecolors="black", linewidths=1.2, zorder=10)381 ax.set_xlim(0, 10); ax.set_ylim(6.19, 0)382 ax.set_xlabel("horizontal x (km)")383 ax.set_ylabel("depth z (km)")384 ax.set_title(f"t = {label}", fontsize=15)385 386 for j in range(n, len(axes)):387 axes[j].set_visible(False)388 389 fig.suptitle("Wavefield time progression โ 3-6 Hz, source @ x=4.958 km\n"390 "(velocity model in grayscale; wavefield amplitude in red/blue)",391 y=1.00, fontsize=17)392 _save(fig, output_dir, "wavefield_time_progression.png")393 394 395# ---------------- Shot gather (paper render style) ----------------396 397def plot_shot_gather(input_dir: Path, output_dir: Path,398 source_indices=GATHER_SOURCE_INDICES,399 position_names=GATHER_POSITION_NAMES,400 source_colors=GATHER_SOURCE_COLORS):401 """1x4 layout: velocity model with color-coded source positions (left)402 + 3 shot-gather panels (Left/Middle/Right matching paper figure)."""403 p_gather = input_dir / "shot_gathers/8s/3-25Hz/shot_gather_cube_gom_151_il_0507.h5"404 p_slice = input_dir / "slices/slice_gom_151_il_0507.h5"405 print(f"Loading shot-gather cube: {p_gather}")406 cube, gather_attrs = _load_h5_dataset(p_gather, "shot_gather_cube")407 n_src, nt, n_rec = cube.shape408 print(f" shape (n_src, nt, n_rec) = ({n_src}, {nt}, {n_rec})")409 print(f"Loading velocity slice for geometry panel: {p_slice}")410 vel, _ = _load_h5_dataset(p_slice, "velocity")411 412 # Source x-positions in km from the gather cube's flat attr (length 64).413 src_x_all = np.asarray(gather_attrs.get("source_horizontal_km", []), dtype=float)414 if src_x_all.size != n_src:415 # Fall back: linear sample if attr missing/wrong length416 src_x_all = np.linspace(0.6, 9.4, n_src)417 rec_z_km = float(gather_attrs.get("receiver_depth_km", 0.01))418 419 delta_t_stored = 14e-3420 total_s = nt * delta_t_stored421 422 # 1 x 4 grid: velocity panel (wider so it stays roughly square)423 # then three gather panels.424 fig, axes = plt.subplots(425 1, 4,426 figsize=(26, 6.8),427 gridspec_kw={"width_ratios": [1.55, 1.0, 1.0, 1.0], "wspace": 0.22},428 )429 430 # ----- Panel 0: velocity model with source positions overlay -----431 ax0 = axes[0]432 extent_vel = (0.0, 10.0, 6.19, 0.0)433 ax0.imshow(vel, cmap="seismic", norm=_vel_norm(),434 extent=extent_vel, aspect="auto", interpolation="lanczos")435 # Streamer (1000 receivers @ 10 m depth, every 25th shown)436 rec_x_all = np.linspace(0.6, 9.4, 1000)437 rec_x_show = rec_x_all[::25]438 ax0.scatter(rec_x_show, np.full_like(rec_x_show, rec_z_km),439 marker="v", s=22, c="black", edgecolors="white",440 linewidths=0.4, zorder=10, label="receivers (every 25th)")441 # Highlight the 3 chosen sources at their actual horizontal_km442 for sidx, color, name in zip(source_indices, source_colors, position_names):443 sx = float(src_x_all[sidx])444 ax0.scatter([sx], [rec_z_km], marker="*", s=520,445 c=color, edgecolors="black", linewidths=1.4, zorder=11,446 label=f"{name} (idx {sidx}, x={sx:.2f} km)")447 ax0.set_xlim(0, 10); ax0.set_ylim(6.19, 0)448 ax0.set_xlabel("horizontal x (km)")449 ax0.set_ylabel("depth z (km)")450 ax0.set_title("Source positions on the velocity model", fontsize=15)451 ax0.legend(loc="lower right", framealpha=0.92, fontsize=10)452 453 # ----- Panels 1-3: gathers -----454 clip = float(np.percentile(np.abs(cube), 97.0))455 if clip <= 0:456 clip = float(np.abs(cube).max() + 1e-12)457 for ax, sidx, label, color in zip(axes[1:], source_indices,458 position_names, source_colors):459 sidx = max(0, min(sidx, n_src - 1))460 gather = cube[sidx]461 ax.imshow(gather, cmap="seismic", vmin=-clip, vmax=clip,462 aspect="auto", interpolation="lanczos",463 extent=[0, n_rec, total_s, 0])464 ax.set_xlabel("receiver index")465 ax.set_ylabel("time (s)")466 for s in ax.spines.values():467 s.set_color(color); s.set_linewidth(2.4)468 ax.set_title(f"{label} (source idx {sidx})",469 color=color, fontweight="bold", fontsize=15)470 471 fig.suptitle("Shot-gather panels โ 3-25 Hz, 64-source cube "472 "(velocity model on left shows the 3 source positions; matches manuscript intro figure)",473 y=1.02, fontsize=17)474 _save(fig, output_dir, "shot_gather_panels.png")475 476 477# ---------------- main ----------------478 479def main():480 ap = argparse.ArgumentParser(description=__doc__,481 formatter_class=argparse.RawDescriptionHelpFormatter)482 ap.add_argument("--input-dir", type=Path, default=Path("./preview_data"),483 help="Dir holding the 4 h5 files. Default: ./preview_data")484 ap.add_argument("--output-dir", type=Path, default=Path("./figs"),485 help="Where to save PNGs. Default: ./figs")486 ap.add_argument("--plot",487 choices=("cube", "orthogonal", "slice", "wavefield", "gather", "all"),488 default="all", help="Which figure to render. Default: all")489 args = ap.parse_args()490 491 if not args.input_dir.exists():492 sys.exit(f"--input-dir does not exist: {args.input_dir}")493 494 plots = ("cube", "orthogonal", "slice", "wavefield", "gather") \495 if args.plot == "all" else (args.plot,)496 for p in plots:497 if p == "cube": plot_3d_cube_cutaway(args.input_dir, args.output_dir)498 elif p == "orthogonal": plot_3d_orthogonal_slices(args.input_dir, args.output_dir)499 elif p == "slice": plot_2d_slice(args.input_dir, args.output_dir)500 elif p == "wavefield": plot_wavefield(args.input_dir, args.output_dir)501 elif p == "gather": plot_shot_gather(args.input_dir, args.output_dir)502 print(f"\nDone. PNGs in {args.output_dir}/")503 504 505if __name__ == "__main__":506 main()507 