API

dasio.dasdata

Universal DAS data container used across all readers and the desample pipeline.

dasio.dasdata.normalize_unit(s)[source]

Map a free-form unit string onto VALID_UNITS, else ‘unknown’.

Case-insensitive substring match, so a vendor’s “Strain rate (microstrain/sec)” comes back as “microstrain/s”.

Return type:

str

class dasio.dasdata.DASmeta[source]

One row of per-file catalog metadata (what DASdb holds).

Dict-shaped so vendor scanners can emit it directly and pandas turns a list of them straight into a DataFrame. For ASN / Proc each file yields exactly one DASmeta; OptaSense yields one per contiguous RawDataTime chunk (same path, different first_sample and nt).

begin_time / end_time are last-sample-inclusive, matching the legacy DAS_db contract.

class dasio.dasdata.DASdata(data: numpy.ndarray, fs: float, dt: float, nt: int, nx: int, dx: float, begin_time: datetime.datetime, end_time: datetime.datetime, gauge_length_m: float | None = None, format: str = 'unknown', origin: str = 'unknown', raw_meta: dict | None = None, t0_sec: float = 0.0, units: str = 'unknown', physical_factor: float = 1.0, index_raw: numpy.ndarray | None = None, dasinfo: dasio.dasinfo.DASinfo | None = None, channel_type: str = 'raw')[source]
Parameters:
  • data (ndarray)

  • fs (float)

  • dt (float)

  • nt (int)

  • nx (int)

  • dx (float)

  • begin_time (datetime)

  • end_time (datetime)

  • gauge_length_m (float | None)

  • format (str)

  • origin (str)

  • raw_meta (dict | None)

  • t0_sec (float)

  • units (str)

  • physical_factor (float)

  • index_raw (ndarray | None)

  • dasinfo (DASinfo | None)

  • channel_type (str)

property info: dict

Snapshot of scalar metadata as a plain dict (no data/raw_meta).

Useful for logging, JSON serialization, and stuffing into downstream metadata records — anything that needs the identifying fields of a DASdata without dragging the array.

times(reftime=None, type='seconds')[source]

The time of every sample.

type='seconds': floats in the record’s own frame (t0_sec at sample 0), or since reftime when one is given – as obspy.Trace.times, nothing on the record changes. type='datetime': absolute datetime64[ns], naive UTC, since numpy carries no zone and every DASdata time is UTC.

Parameters:
  • reftime (datetime | None)

  • type (str)

Return type:

ndarray

property reftime: datetime

what times(), plot labels and a float t_range count from. Assign a datetime to move it, an event’s origin time usually; it only rewrites t0_sec.

Type:

The instant at t = 0 of the seconds frame

channels(type=None)[source]

Channel number of every row.

Parameters:

type (str | None) – ‘raw’ (the interrogator’s numbering) or ‘taptest’ (the catalog’s, after select_taptest). Default is channel_type, which is also what select’s ch_range and ch_index match.

Raises:

ValueError – ‘taptest’ asked for before a catalog was attached.

Return type:

ndarray

property ch0: int

Optical channel of row 0, read off index_raw.

property dch: int

Mean raw channel step; exact while the rows are a ramp.

Kept because a texture and an imshow extent can only be placed on a uniform grid, so they need one spacing rather than a list.

property plot

d.plot() (≡ d.plot.imshow()), d.plot.imshow(...), d.plot.wiggle(...).

Implementation lives in dasio.plot; lazy-imported so a bare import dasdata doesn’t pull in matplotlib.

Type:

Plot accessor

select(ch_range=None, ch_index=None, t_range=None)[source]

Cut a time window and / or pick channels, in one call. :param ch_range: (min_ch, max_ch), max exclusive, in the channel numbers

channels() reports. Ends outside the array are clipped.

Parameters:
  • ch_index – explicit channel numbers, in the order wanted, or a length-nx boolean mask (which selects by row position). A number the array does not hold raises.

  • t_range – (begin, end), both datetime, or both seconds in this DASdata’s own frame where t0_sec is the value at sample 0.

Returns:

A fresh DASdata. dt, fs and dx are unchanged — this selects, it does not decimate — and data is C-contiguous.

Raises:

ValueError – if the two channel arguments together select nothing.

Return type:

DASdata

The kept channels are the intersection: ch_range filters, ch_index requests, and either may be None for no constraint. A channel named by ch_index but excluded by ch_range is dropped rather than raising — that is what the pair is for.

truncate(ch_range=None, t_range=None)[source]

A contiguous window — select(ch_range=..., t_range=...).

Return type:

DASdata

select_taptest(dasinfo)[source]

Keep the surveyed channels a DASinfo lists, and report their index.

Parameters:

dasinfo – the catalog. Pass dasinfo.active() to drop bad-quality channels too; located() on that subset is a no-op.

Returns:

A fresh DASdata with dasinfo attached and channel_type set to ‘taptest’.

Return type:

DASdata

Intersected, not demanded: a catalog covers the whole deployment and this window holds a part of it.

skip_ch(step)[source]

Keep every step-th channel (uniform decimation), updating dx.

A coarse, faster channel view — d.skip_ch(5) keeps 1 channel in 5. Because the stride is uniform, dx scales by step so the channel axis stays physically correct (unlike an arbitrary select, which leaves dx meaningless). No spatial anti-alias filter is applied, so this is for display/preview, not analysis. step <= 1 returns an unchanged copy. data is C-contiguous.

Parameters:

step (int)

Return type:

DASdata

skip_t(step)[source]

Keep every step-th time sample (uniform decimation), updating dt/fs.

A coarse, faster time view — d.skip_t(5) keeps 1 sample in 5. dt scales by step and fs divides by it, so the time axis stays correct; begin_time/t0_sec are unchanged (sample 0 is kept) and end_time snaps to the last kept sample. No temporal anti-alias filter is applied, so this is for display/preview, not analysis (use a proper resample/decimate for that). step <= 1 returns an unchanged copy. data is C-contiguous.

Parameters:

step (int)

Return type:

DASdata

to_physical()[source]

Return a copy in microstrain (or microstrain/s), whatever the vendor.

Applies physical_factor and the strain -> microstrain 1e6 in one float32 pass, resets the factor to 1.0 and updates units. Counts, radians and strain all land on the same unit, so downstream code and colorbars never have to ask which vendor a window came from.

A metadata-only copy when the data is already microstrain — sharing data rather than duplicating it — so calling this unconditionally is both safe and idempotent.

Raises:

ValueError – On count / radian / radian/s with no factor attached (physical_factor == 1.0), rather than silently relabeling raw counts as microstrain. Read with with_factor=True (the default).

Return type:

DASdata

bandpass(fmin, fmax, order=14, zerophase=True, copy=True, nthreads=None)[source]

Butterworth bandpass along the time axis. See processing.bandpass.

Parameters:
  • fmin (float)

  • fmax (float)

  • order (int)

  • zerophase (bool)

  • copy (bool)

  • nthreads (int | None)

Return type:

DASdata

detrend(copy=True)[source]

Per-channel linear detrend along time. See processing.detrend.

Parameters:

copy (bool)

Return type:

DASdata

taper(alpha=0.4, copy=True)[source]

Tukey edge taper along time. See processing.taper.

Parameters:
  • alpha (float)

  • copy (bool)

Return type:

DASdata

differentiate(copy=True, method='central')[source]

Time-axis derivative. See processing.differentiate.

Parameters:
  • copy (bool)

  • method (str)

Return type:

DASdata

integrate(copy=True)[source]

Time-axis cumulative integral. See processing.integrate.

Parameters:

copy (bool)

Return type:

DASdata

subtract_common_mode(ch_min=0, ch_max=None, copy=True)[source]

Common-mode noise rejection. See processing.subtract_common_mode.

Parameters:
  • ch_min (int)

  • ch_max (int | None)

  • copy (bool)

Return type:

DASdata

unwrap_int32(factor=1, copy=True)[source]

OptaSense int32 phase-wrap correction. See processing.unwrap.

Parameters:
  • factor (int)

  • copy (bool)

Return type:

DASdata

median_filter_1d(kernel_size, axis='t', copy=True)[source]

Running median along time or channels. See processing.median_filter.

Parameters:
  • kernel_size (int)

  • axis (str)

  • copy (bool)

Return type:

DASdata

downsample(factor, anti_alias=True, order=8, zerophase=True, copy=True, nthreads=None)[source]

Integer-factor time downsample: anti-alias low-pass + stride.

Analysis-grade decimation — unlike skip_t (a bare stride that aliases), this low-passes below the new Nyquist first. See processing.downsample.

Parameters:
  • factor (int)

  • anti_alias (bool)

  • order (int)

  • zerophase (bool)

  • copy (bool)

  • nthreads (int | None)

Return type:

DASdata

dasio.dasfile

DASFile: per-file I/O facade.

Wraps one .h5 / .hdf5 path. format (on-disk format) and origin (original capture vendor) are detected lazily — only on attribute access — so construction is free. This matters for directory-scan hot paths (DASdb.scan_metadata) that build thousands of DASFile`s in a loop: eager detection would cost one extra h5py open per file on top of the `.metadata() read itself.

Thin glue over the vendor-specific readers + the OptaSense count-to- strain factor. This is the canonical entry point for working with a single DAS file. The free-function dispatchers in dasio/__init__.py (read_das_data, read_das_metadata, factor_raw2strain) are thin wrappers around it — kept for one-shot call sites.

class dasio.dasfile.DASFile(path, format=None, origin=None)[source]

One DAS HDF5 file with lazy format + origin detection.

format is the on-disk format ('ASN', 'OptaSense', 'Proc', 'Basic', …) and decides which reader dispatches. origin is the vendor that originally captured the data — equal to format for raw files, recovered from /Acquisition_origin attrs for Proc files. It picks the raw->strain factor, and DASdata carries the same pair so a window keeps both facts after the file is closed.

Both attributes are detected on first access and cached. Passing them up-front skips the detection open entirely — .metadata() needs only format, .factor() needs only origin.

Parameters:
  • path (Union[str, Path]) – Filesystem path to the .h5 / .hdf5 file.

  • format (Optional[str]) – Pre-known format. If omitted, detected via detect_format on first access.

  • origin (Optional[str]) – Pre-known origin vendor. If omitted, detected via detect_origin on first access.

read(*, with_factor=True, **kwargs)[source]

Load the payload as a DASdata.

Keyword arguments pass through to the vendor reader (min_ch, max_ch, first_sample, n_samples).

with_factor=True (default) attaches DASFile.factor() as DASdata.physical_factor, which is what makes .to_physical() work without the caller having to re-read the file. It costs one extra open — 0.6 ms against a ~550 ms payload read — so the default is on and desample opts out explicitly, being the one path that wants raw counts on disk.

It is attached only for the instrument units in _NEEDS_FACTOR; strain payloads need no vendor factor, only the 1e6 that to_physical() applies.

Parameters:

with_factor (bool)

Return type:

DASdata

metadata()[source]

Return a DASmeta dict (ASN / Proc) or list of them (OptaSense when the file holds multiple RawDataTime chunks).

Return type:

DASmeta | List[DASmeta]

factor()[source]

Scalar factor to convert the raw payload to strain.

Keyed by origin, so a Proc file whose raw capture came from OptaSense still gets the count→strain conversion. Returns 1.0 for vendors whose payload already is strain / strain-rate.

Return type:

float

dasio.dasdb

DASdb: catalog of raw DAS files + RawWindow read-plan helpers.

Model:

DASmeta one contiguous readable piece of raw data (one ASN / Proc

file, or one RawDataTime chunk within an OptaSense file). Defined in dasdata.py as a TypedDict so scanners can emit a list of dicts and pandas consumes them directly.

DASdb DataFrame of DASmeta rows for one vendor. Time-contiguous

runs are recomputed on the fly by segments; nothing is persisted as a segment_id column.

RawWindow selected read plan that desample.desample_window

consumes — files + per-row slice offsets + pad flags.

Vendor-specific logic lives only in list_das_files and the per- file metadata readers. The catalog and all queries (select_window, read, plus the desample-side next_unprocessed selector that consumes DASdb) work off DataFrame rows regardless of vendor.

Time convention: begin_time and end_time are last-sample-inclusive timestamps, matching legacy DAS-utilities (DAS_db.py). Continuity between two rows is therefore next.begin_time - prev.end_time ≈ 1/fs, not ≈ 0.

File metadata (fs, nt, channel count, etc.) is populated at scan time from each file’s HDF5 metadata — filename parsing is never used as a correctness fallback; a file that can’t be read is skipped with a stderr warning.

dasio.dasdb.list_das_files(raw_dir, format)[source]

Enumerate candidate DAS files under raw_dir for format.

Pure filesystem work — no HDF5 opens. Paired with DASFile.metadata() to drive incremental catalog refresh without reopening files already in the DASdb.

Parameters:
  • raw_dir (Path)

  • format (str)

Return type:

List[Path]

dasio.dasdb.detect_dir_format(raw_dir)[source]

On-disk format of the first DAS file found under raw_dir.

Depth 1 (flat Proc / OptaSense), 2 (dated Proc, plain ASN) and 3 (ASN with a channel subdir: <YYYYMMDD>/<ch>/<HHMMSS>.hdf5). glob is lazy and next stops at the first hit, so the deep patterns cost nothing on the shallow layouts.

Parameters:

raw_dir (str | Path)

Return type:

str

dasio.dasdb.scan_metadata(raw_dir, format, workers=1, progress=False)[source]

Scan raw_dir for all format files and return a DASmeta DataFrame.

Enumerates files via list_das_files, reads each through the DASFile.metadata() facade, concatenates OptaSense RawDataTime splits, and sorts by begin_time. Files that fail metadata read are skipped (the vendor metadata readers emit a stderr warning). workers > 1 parallelizes the per-file reads; progress=True adds a tqdm bar.

raw_dir is resolved to an absolute path before enumeration so every row’s file field is absolute regardless of the caller’s cwd. Without this, a catalog written from a relative --from would only resolve correctly when re-read from the same working dir.

Raises ValueError when the first readable file is not format, rather than opening thousands of them to produce an empty catalog.

Parameters:
  • raw_dir (Path)

  • format (str)

  • workers (int)

  • progress (bool)

Return type:

DataFrame

class dasio.dasdb.DASdb(df, format)[source]

Catalog of DAS file metadata rows, partitioned on the fly.

Parameters:
  • df (pd.DataFrame) – DataFrame with the _DF_COLUMNS schema (one row per DASmeta). Missing columns fall back to NaN / 0 where a default is meaningful.

  • format (str) – ASN, OptaSense, or Proc (matches the scanner used to build df).

property n_segments: int

Number of time-contiguous segments — recomputed on access.

segments(gap_factor=1.5)[source]

Yield (begin_time-sorted) DataFrames of time-contiguous rows.

One segment = one continuous run of rows with no acquisition gap. Break criteria between consecutive rows (prev → cur):

gap_factor x 1/fs (default tolerates roughly one sample of jitter; raise gap_factor to fuse near-contiguous segments). * fs or nx change — acquisition parameters changed. * same file path — OptaSense intra-file RawDataTime breaks always start a new segment regardless of gap size.

Recomputed on every call; DASdb never persists a segment_id.

Implementation note: the break-detection rules are evaluated vectorized over the whole catalog with Series.shift(1) — on Santorini’s 1.13 M-row production catalog the 24 h slice (~8.6 k rows) goes from ~1 s of GIL-held Python in the pre-vectorize row-by-row loop to ~5 ms. The slow loop was amplifying every /api/state poll’s _read_gaps_24h call, saturating one CPU and freezing the dashboard whenever a user clicked the gaps tab. See test/test_dasdb_segments_perf.py for the regression guard.

Parameters:

gap_factor (float)

Return type:

Iterator[DataFrame]

classmethod from_dir(raw_dir, format=None, workers=1, progress=True)[source]

Scan raw_dir and build a catalog for the given format.

Parameters:
  • raw_dir (Path)

  • format (str | None)

  • workers (int)

  • progress (bool)

Return type:

DASdb

classmethod from_csv(file, format=None)[source]

Load a legacy DAS_db CSV (whitespace-separated).

Translates legacy column names (begTime, endTime, nChannels, dCh, GaugeLen, firstSample) into the internal snake_case schema. Drops any segment_id or format columns the CSV might carry — neither is part of the in-memory schema. If format is given and the CSV has a format column, rows are filtered to that format before the column is dropped.

Parameters:
  • file (str | Path)

  • format (str | None)

Return type:

DASdb

to_csv(file)[source]

Write a DAS_db CSV (whitespace-separated, one row per DASmeta).

Emits legacy column names (begTime, endTime, nChannels, dCh, GaugeLen, firstSample) so external DAS-utilities tooling can still read the output. Missing numeric fields get the legacy -1 sentinel (rather than empty, which would collapse under whitespace-separation). Sorted by begin_time; atomic write.

Parameters:

file (str | Path)

Return type:

None

classmethod from_parquet(file, format=None)[source]

Load a DASdb Parquet file.

format is recovered from the b’format’ file-level metadata key written by to_parquet; the kwarg is a fallback for foreign parquet files without that key, and a conflict between the two raises.

Parameters:
  • file (str | Path)

  • format (str | None)

Return type:

DASdb

to_parquet(file)[source]

Write a DASdb to a Parquet file (snake_case schema, snappy).

format rides along in file-level metadata under b’format’. Sorted by begin_time; atomic write.

Parameters:

file (str | Path)

Return type:

None

classmethod from_file(file, format=None)[source]

Load a catalog by path; format chosen from extension.

Parameters:
  • file (str | Path)

  • format (str | None)

Return type:

DASdb

to_file(file)[source]

Persist the catalog; format chosen from extension.

Parameters:

file (str | Path)

Return type:

None

append(df_new)[source]

Concatenate df_new rows into the catalog.

De-duplicates on (file, first_sample) — OptaSense emits multiple rows per file (one per RawDataTime chunk), so file alone is not unique. Sort order is preserved by begin_time.

Parameters:

df_new (DataFrame)

Return type:

None

update_from_dir(raw_dir, workers=1, progress=False)[source]

Incrementally add any new files from raw_dir.

Lists the candidate files on disk and only opens the ones whose path isn’t already in self.df['file'] — turns a full O(n_total) rescan into O(n_new) HDF5 opens. Safe to call on every cron tick; continuity is recomputed on the fly by segments, so gaps that open or close in the newly discovered tail are reflected immediately. workers > 1 parallelizes the per-file metadata reads; progress=True wraps the iteration with a tqdm bar.

Returns the number of rows added (0 if nothing was new).

Parameters:
  • raw_dir (Path)

  • workers (int)

  • progress (bool)

Return type:

int

classmethod load_or_build(file, raw_dir, format, overwrite=False, workers=1, progress=False)[source]

Load an existing catalog (+ update) or scan raw_dir fresh.

  • file=None — fresh scan, no persistence.

  • file exists + !overwrite → load (csv or parquet by extension) + update_from_dir.

  • file missing, or overwrite → fresh from_dir.

Never writes; caller persists via db.to_csv / db.to_parquet / db.to_file.

Parameters:
  • file (Path | None)

  • raw_dir (Path)

  • format (str)

  • overwrite (bool)

  • workers (int)

  • progress (bool)

Return type:

DASdb

select_window(begin, end, pad=True)[source]

One RawWindow per segment intersecting [begin, end).

Rows whose [begin_time, end_time] intersects [begin, end) are the target set. Pad files are added from the SAME segment only — neighbours across a gap are never pulled in, so the returned list makes each segment’s own pad-before/pad-after state explicit instead of silently dropping one.

Most callers (the next_unprocessed → desample_window path) ask for a window that falls entirely in one segment and just take [0]; multi-segment callers iterate.

Raises RuntimeError when no segment intersects.

Parameters:
  • begin (datetime)

  • end (datetime)

  • pad (bool)

Return type:

List[RawWindow]

read(begin_time, end_time, min_ch=0, max_ch=None, fill_gap=True, with_factor=True)[source]

Read a concatenated DASdata covering [begin, end).

Vendor-agnostic time-range query. Walks every segment that overlaps the window; for each intersecting row the exact first_sample offset and n_samples count are computed from the row’s begin_time / fs / nt, so DASFile.read returns only the samples that fall inside the window — no post-read trim.

If the window straddles acquisition gaps:

  • fill_gap=True (default) — each inter-segment gap is

filled with zeros so the returned data has a uniformly- spaced time axis running from the first kept sample onward. * fill_gap=False — gaps are dropped and the output is shorter than (end - begin) x fs. end_time reports the last sample’s true timestamp, so callers can detect the shortening.

with_factor=True (default) attaches the raw->physical conversion as physical_factor, so .to_physical() reaches microstrain without a re-read. It is not applied here: OptaSense counts must be `unwrap`ed on the concatenated array first, and scaling before that would put the 2**32 rails at unrecognizable values.

Raises RuntimeError if no files intersect the window.

Parameters:
  • begin_time (datetime)

  • end_time (datetime)

  • min_ch (int)

  • max_ch (int | None)

  • fill_gap (bool)

  • with_factor (bool)

Return type:

DASdata

plot_timeline(ax=None, by_month=None)[source]

Gantt-style coverage timeline. See dasio.plot.plot_timeline.

Lazy-imported so a bare import dasio.dasdb stays clear of matplotlib.

dasio.dasdb.main(argv=None)[source]

python -m dasio.dasdb — build or update a DASdb catalog.

Format dispatch is by file extension: .parquet/.parq writes parquet, .csv/.txt/no-suffix writes legacy whitespace CSV. Default behaviour is incremental: if the target file already exists we load it and only open the HDF5 files that are new on disk. Pass –overwrite to force a full rescan.

One-shot format conversion of an existing catalog is a one-liner:
python -c “from dasio.dasdb import DASdb;

DASdb.from_file(‘old.csv’, format=’ASN’).to_file(‘new.parquet’)”

dasio.dasinfo

Channel-metadata catalog for one snapshot of an instrument configuration.

DASinfo wraps a pandas DataFrame indexed by index_raw (the row position in the reader’s raw (nx_raw, nt) array). Each row has:

fiber           'n' / 's' / 'c' / future 'e' / 'w'. 'u' marks an unknown
                fiber (NaN rows in the CSV are mapped to 'u'). Single-
                fiber rigs default to 'n' when the input CSV lacks a
                fiber column.
taptest         1 if a taptest survey produced a geolocation, else 0.
                Defaults to 1 when the CSV lacks the column.
quality         1 if the located channel is currently good for downstream
                processing, 0 if known noisy / muted. Only meaningful
                when taptest == 1; forced to 0 when taptest == 0.
                Defaults to 1 when the CSV lacks the column.
index_taptest   Sequential integer per fiber within taptest==1 rows.
                NA when taptest == 0. Preserved verbatim if the CSV
                already supplies the column; otherwise computed.
lat, lon        WGS84 from the taptest survey. NaN when taptest == 0.

Subset operations (by_fiber, located, active) preserve index_raw and index_taptest — they shrink the row count without renumbering.

For cross-instrument-config durability, the join key on located channels is the composite (fiber, index_taptest) — frozen at survey time. index_raw shifts when fiber is added or removed on the instrument side.

class dasio.dasinfo.DASinfo(df)[source]

Immutable channel catalog: DASinfo(df) or DASinfo.from_csv(path).

Any channel table is normalized into the canonical schema on construction.

select_taptest needs index_raw and taptest; plot, coord_lookup and projection_origin need lat (or latitude) and lon (or longitude). Columns defaulted when absent:

fiber defaults to ‘n’. taptest defaults to 1. quality defaults to 1. Forced to 0 wherever taptest==0. index_raw defaults to row position (0..N-1). index_taptest computed per-fiber starting at 0 for

taptest==1 rows when missing.

Rename aliases (source column dropped after rename):

index -> index_raw ichan_before_taptest -> index_raw ichan_after_taptest -> index_taptest taptest_channel_index -> index_taptest status -> taptest (string ‘good’ / ‘bad’

converted to 1 / 0)

spiky -> quality (polarity-flipped:

spiky=0 -> quality=1; spiky=1 -> 0)

latitude -> lat longitude -> lon elevation -> ele azimuth -> azi dipping angle -> dip

Production LF / EQ deployment CSVs (Iceland + Santorini) load directly through these aliases — no CSV mutation needed before consumers migrate to DASinfo.from_csv.

Parameters:

df (DataFrame)

by_fiber(letter)[source]

Rows where fiber == letter. Empty result is allowed.

Parameters:

letter (str)

Return type:

DASinfo

located()[source]

Rows where taptest == 1 (channel has a geolocation).

Return type:

DASinfo

active()[source]

Rows where taptest == 1 AND quality == 1. The set fed to the picker / locator in production.

Return type:

DASinfo

property index_raw: ndarray

index_raw values for the current rows, as a numpy int64 array.

Use to slice the reader’s (nx_raw, nt) output array down to this DASinfo’s subset:

>>> d_raw = reader(path)                                        # shape (nx_raw, nt)
>>> d = d_raw[dasinfo.active().index_raw, :]
>>> # row k of d corresponds to dasinfo.active().df.index[k]
coord_lookup()[source]

lat/lon columns indexed by index_raw. Suitable for joining picks (which carry index_raw) to coordinates.

Return type:

DataFrame

property projection_origin: tuple[float, float]

Mean (lat, lon) over located channels. Raises if none are located.

plot(*, every=500, index='taptest', ax=None, **kwargs)[source]

Scatter-plot every Nth located channel on a (lon, lat) map.

Parameters:
  • 500. (every stride. Default)

  • 'taptest' (index) – fiber; selected channels are located by construction. ‘raw’ strides index_raw and skips rows without a geolocation (taptest == 0).

  • None. (ax matplotlib Axes to draw on; created if)

  • marker (**kwargs forwarded to ax.scatter; defaults to red) – with black edge.

  • every (int)

  • index (str)

Annotation labels are <fiber><index_taptest> for taptest mode and the bare <index_raw> integer for raw mode. Returns (fig, ax).

dasio.processing

High-level DAS processing — DASdata in, DASdata out.

Thin wrappers around the numeric kernels in signal.py that preserve the DASdata envelope (fs, dt, nx, nt, dx, begin_time, end_time, gauge_length_m, format, raw_meta) so pipelines read naturally:

d = read_das_data(path, ‘Proc’) d = bandpass(d, 0.01, 0.4) d = integrate(d)

Each function returns a new DASdata via dataclasses.replace. Pass copy=False when you know the input’s data can be thrown away — the kernels themselves return fresh arrays either way, but copy=True (the default) guards against later aliasing surprises when the caller mutates the input in place.

dasio.processing.bandpass(d, fmin, fmax, order=14, zerophase=True, copy=True, nthreads=None)[source]

Butterworth bandpass along the time axis.

nthreads sets the channel-parallel OpenMP fan-out in the C++ kernel. None (default) resolves to default_nthreads() — the kernel’s own default is 1 thread, which leaves most of the machine idle on a filter that is embarrassingly parallel over channels. Pass an explicit integer to throttle on a shared host, or when calling this from inside worker processes that already partition the cores (as desample does).

Parameters:
  • d (DASdata)

  • fmin (float)

  • fmax (float)

  • order (int)

  • zerophase (bool)

  • copy (bool)

  • nthreads (int | None)

Return type:

DASdata

dasio.processing.detrend(d, copy=True)[source]

Subtract a per-channel linear trend along the time axis.

Standard pre-processing step before bandpass — removes any DC offset and slow drift that would otherwise leak into the filter transition band. Mirrors legacy DASutils.detrend_2D.

Parameters:
Return type:

DASdata

dasio.processing.taper(d, alpha=0.4, copy=True)[source]

Tukey edge taper along the time axis.

alpha is the fraction of the window covered by the cosine transition at each end (0 = no taper, 1 = full Hann). Default 0.4 matches legacy DASutils.readFile_HDF. Apply between detrend and bandpass to suppress filter ringing at the segment boundaries.

Parameters:
  • d (DASdata)

  • alpha (float)

  • copy (bool)

Return type:

DASdata

dasio.processing.differentiate(d, copy=True, method='central')[source]

Differentiate along the time axis (d/dt).

method="central" (default) uses the second-order central difference (signal.gradient_time, bit-identical to np.gradient). "backward" uses the first-order backward difference with the first sample forced to zero (signal.diff_time, matching DASutils.preprocess_diff).

Parameters:
  • d (DASdata)

  • copy (bool)

  • method (str)

Return type:

DASdata

dasio.processing.integrate(d, copy=True)[source]

Integrate along the time axis (cumulative sum × dt).

First time sample is forced to zero so the trace is pinned to the window’s start — otherwise a DC offset in d.data would run off linearly and dominate the result.

Parameters:
Return type:

DASdata

dasio.processing.subtract_common_mode(d, ch_min=0, ch_max=None, copy=True)[source]

Per-time-sample common-mode noise subtraction across channels.

For each time sample we take the median across channels [ch_min, ch_max) and subtract it from every channel of d. The slice bounds let callers estimate from a quiet stretch of fiber rather than the whole array — biases the median toward background rather than active signal. Default bounds use all channels (matches legacy DASutils.preprocess_medfilt).

Parameters:
  • d (DASdata)

  • ch_min (int)

  • ch_max (int | None)

  • copy (bool)

Return type:

DASdata

dasio.processing.unwrap_int32(d, factor=1, copy=True)[source]

Unwrap int32 rollover along the time axis.

Needed for raw OptaSense phase counts; a no-op for data that’s already strain. factor scales the 2**32 wrap increment (default 1, matching standard OptaSense int32).

Thin wrapper over signal.unwrap_int32, which works in place; copy=True (default) protects the caller’s array, copy=False unwraps d.data where it lies.

Parameters:
  • d (DASdata)

  • factor (int)

  • copy (bool)

Return type:

DASdata

dasio.processing.downsample(d, factor, anti_alias=True, order=8, zerophase=True, copy=True, nthreads=None)[source]

Integer-factor downsample along time: anti-alias low-pass, then stride.

The analysis-grade counterpart to DASdata.skip_t, which is a bare stride and therefore aliases. With anti_alias=True (default) a zero-phase Butterworth low-pass at fs / (2.5 * factor) is applied first, reusing the C++ filter — a band-pass with the high-pass corner disabled (freqmin=0) is a pure low-pass. That cutoff is 0.8x the new Nyquist (fs / factor / 2), the same 2.5x-oversampling guard band as desample.desample_window, so the order-order Butterworth has room for its transition roll-off before aliased energy can fold back. fs, dt, nt and end_time are then updated by reusing skip_t; begin_time and t0_sec are unchanged (sample 0 is kept).

Set anti_alias=False to skip the filter when the data is already band-limited below the new Nyquist (e.g. you just band-passed) — then this is exactly skip_t in the same call. factor <= 1 is a no-op. The C++ filter needs float32/float64 data, like bandpass.

nthreads is forwarded to the anti-alias filter; see bandpass.

Parameters:
  • d (DASdata)

  • factor (int)

  • anti_alias (bool)

  • order (int)

  • zerophase (bool)

  • copy (bool)

  • nthreads (int | None)

Return type:

DASdata

dasio.processing.median_filter_1d(d, kernel_size, axis='t', copy=True)[source]

Running median along time (axis='t') or channels (axis='x').

Suppresses spikes narrower than the kernel while leaving genuine edges intact — the thing a band-pass cannot do, since a band-pass smears an impulse across its whole passband instead of removing it. kernel_size must be odd. See signal.median_filter_1d.

Parameters:
  • d (DASdata)

  • kernel_size (int)

  • axis (str)

  • copy (bool)

Return type:

DASdata

dasio.plot

Minimal plotting helpers for DASdata.

Two free functions (imshow, wiggle) and a bound _PlotAccessor exposed via DASdata.plot. Matplotlib is imported lazily inside each function body so a bare import dasio (or import dasdata) stays free of the matplotlib startup cost — the load only fires when an actual plot is requested.

plot_xcorr images a cross-correlation gather (the cc array of a dasio.noise.CCGather); CCGather.plot() is the convenience wrapper around it. plot_spectrogram images the (freq, times, S) result of psd.spectrogram / psd.spectrogram_cwt, and plot_fk / plot_fc the f-k and dispersion panels from dasio.fk.

dasio.plot.imshow(d, ax=None, ch_range=None, t_range=None, skip_ch=1, skip_t=1, style='seismic', usedatetime=False, perc=99.5, vmin=None, vmax=None, cmap='seismic', cbar=True, figsize=None, **kwargs)[source]

Color-mapped image of a DASdata (regular grid).

Parameters:
  • d (DASdata)

  • ax (matplotlib Axes, optional) – Plot target; one is created if not given.

  • ch_range (tuples, optional) – Forwarded to DASdata.truncate for the displayed window. For an arbitrary channel subset, pre-select with d.select(ch_index=...) and plot the result.

  • t_range (tuples, optional) – Forwarded to DASdata.truncate for the displayed window. For an arbitrary channel subset, pre-select with d.select(ch_index=...) and plot the result.

  • skip_ch (int, default 1) – Plot only every skip_ch-th channel / skip_t-th time sample — a coarser, faster raster for big arrays (e.g. skip_t=5 on a wide window). Reuses DASdata.skip_ch / .skip_t. The percentile and the image both run on the decimated array, so this genuinely speeds the plot up; the channel/time axes still span the full range. 1 (default) draws every sample.

  • skip_t (int, default 1) – Plot only every skip_ch-th channel / skip_t-th time sample — a coarser, faster raster for big arrays (e.g. skip_t=5 on a wide window). Reuses DASdata.skip_ch / .skip_t. The percentile and the image both run on the decimated array, so this genuinely speeds the plot up; the channel/time axes still span the full range. 1 (default) draws every sample.

  • style ({'seismic', 'normal'}) – 'seismic' (default, matches legacy DASutils) puts channels on the x-axis and time on the y-axis with time growing downward — the traditional seismic-record orientation. 'normal' puts time on the x-axis and channels on the y-axis — easier to read as a stack of time-series.

  • usedatetime (bool) – Show the time axis as datetime64 ticks (True) instead of seconds-from-t0_sec (False, default).

  • perc (float) – Percentile (of |data|) used to set symmetric vmin / vmax when the latter aren’t provided. Default 99.5 trims out the loudest 0.5 % so background noise stays visible.

  • vmin (floats, optional) – Manual color-scale bounds; bypass perc when both are given.

  • vmax (floats, optional) – Manual color-scale bounds; bypass perc when both are given.

  • cmap (str or Colormap) – Default 'seismic' (red-white-blue), matches legacy DASutils.

  • cbar (bool)

  • figsize ((w, h), optional) – Figure size when ax is created here. Defaults to (8, 4) for style='seismic' and (4, 8) for style='normal'. Ignored when ax is supplied.

  • **kwargs (forwarded to ax.imshow)

Return type:

(ax, im) so callers can attach annotations / set titles / etc.

dasio.plot.wiggle(d, ax=None, ch_range=None, t_range=None, n_max_ch=100, max_nt=5000, style='seismic', scale=1.0, color='k', lw=0.6, usedatetime=False, normalize=True, figsize=None, **kwargs)[source]

Per-channel wiggle traces of a DASdata.

Each retained channel becomes a line offset by its (original) channel index, optionally amplitude-normalized so all traces share the same visual range.

style='seismic' (default) places channels on the x-axis and time on the y-axis growing downward — the traditional seismic record orientation. style='normal' places time on the x-axis and channel on the y-axis (descending), which reads more like a stack of time-series.

n_max_ch caps the number of traces drawn — when sub.nx > n_max_ch the channels are decimated with stride ceil(sub.nx / n_max_ch). Defaults to 100; pass None to draw every channel. This stops a casual d.plot.wiggle() on a full-array DASdata from rendering thousands of overlapping lines. Use ch_range first if you want a specific window.

max_nt caps the number of time samples drawn per trace — when sub.nt > max_nt the time axis is strided by ceil(sub.nt / max_nt). Wiggle render cost scales with n_traces * n_samples, and at screen resolution a few thousand points per trace is already pixel-lossless, so this is the main speed lever on wide time windows. Defaults to 5000; pass None to draw every sample.

Parameters:
  • ch_range (Tuple[int, int] | None)

  • t_range (Tuple | None)

  • n_max_ch (int | None)

  • max_nt (int | None)

  • style (str)

  • scale (float)

  • color (str)

  • lw (float)

  • usedatetime (bool)

  • normalize (bool)

  • figsize (Tuple[float, float] | None)

dasio.plot.plot_xcorr(cc, lag_s=None, fs=None, offsets=None, ax=None, perc=99.0, vmin=None, vmax=None, cmap='seismic', cbar=True, figsize=None, **kwargs)[source]

Image a cross-correlation gather — pair (x) vs lag time (y, downward).

Plots the (npair, nlag) array returned by dasio.noise.xcorr_stack (the first tuple element) or xcorr_dataset (result['cc']): each column is one channel pair’s stacked correlation, zero lag at the centre row. Lag time runs down the vertical axis (seismic-gather convention) and pairs along the horizontal axis, so a common-shot gather shows the virtual-source moveout fanning out symmetrically in +/- lag.

Parameters:
  • cc ((npair, nlag) array.)

  • lag_s (lag axis in seconds (e.g. xcorr_dataset’s result['lag_s']).) – If None it is built from fs (or falls back to sample indices), with zero lag at the centre. Drawn on the vertical axis, increasing downward.

  • fs (sample rate, used to label the lag axis in seconds when lag_s is None.)

  • offsets (optional per-pair x values (e.g. inter-channel offset in km for a) – common-offset gather); defaults to the pair index.

  • perc (as in imshow — perc sets) – symmetric colour limits from the perc-th percentile of |cc| unless vmin/vmax are given.

  • vmin (as in imshow — perc sets) – symmetric colour limits from the perc-th percentile of |cc| unless vmin/vmax are given.

  • vmax (as in imshow — perc sets) – symmetric colour limits from the perc-th percentile of |cc| unless vmin/vmax are given.

  • cmap (as in imshow — perc sets) – symmetric colour limits from the perc-th percentile of |cc| unless vmin/vmax are given.

  • cbar (as in imshow — perc sets) – symmetric colour limits from the perc-th percentile of |cc| unless vmin/vmax are given.

  • figsize (as in imshow — perc sets) – symmetric colour limits from the perc-th percentile of |cc| unless vmin/vmax are given.

  • **kwargs (forwarded to ax.imshow.)

  • ``(ax (Returns) –

  • im)``.

dasio.plot.plot_spectrogram(S, freq=None, times=None, ich=0, t0=None, usedatetime=None, ax=None, log_color=True, period_axis=None, perc=99.5, vmin=None, vmax=None, cmap='turbo', cbar=True, units='', figsize=None, **kwargs)[source]

Image a time-resolved PSD from psd.spectrogram or psd.spectrogram_cwt.

Data first, like the other plotters here — either the whole result or the array plus its axes:

plot_spectrogram(spectrogram_cwt(d.data, d.fs, 1/200, 0.5)) plot_spectrogram(S, freq, times, t0=d.start_time)

Parameters:
  • S ((nchan, nfreq, ntime) or (nfreq, ntime) array, or the whole) – (freq, times, S) tuple those functions return (accepted as-is when freq/times are not given).

  • freq (the frequency (Hz) and time (s) axes; taken from the result) – tuple, or bin/sample indices when absent.

  • times (the frequency (Hz) and time (s) axes; taken from the result) – tuple, or bin/sample indices when absent.

  • ich (channel to draw when S is 3-D.)

  • t0 (optional absolute start time — np.datetime64, datetime, ISO) – string, or unix seconds. Turns the x-axis into calendar time.

  • usedatetime (calendar-time x-axis, as in imshow. Default None means) – “whenever t0 is given”. True without t0 raises — times holds seconds from the record start, so the epoch must come from somewhere. False keeps the seconds axis even when t0 is passed.

  • log_color (color by log10(S) (default) rather than linear PSD.)

  • period_axis (log-frequency axis labeled in seconds of period. Default) – None auto-enables it for a log-spaced grid (spectrogram_cwt) and leaves a linear grid (spectrogram) on a linear frequency axis.

  • perc (color limits; unless given, the perc-th and) – (100-perc)-th percentiles of the finite color values.

  • vmin (color limits; unless given, the perc-th and) – (100-perc)-th percentiles of the finite color values.

  • vmax (color limits; unless given, the perc-th and) – (100-perc)-th percentiles of the finite color values.

  • cmap (as in imshow.)

  • cbar (as in imshow.)

  • figsize (as in imshow.)

  • units (physical unit of the data (e.g. 'Pa') for the colorbar label,) – shown as units^2/Hz.

  • **kwargs (forwarded to ax.pcolormesh (rasterized defaults to True —) – thousands of quads otherwise swamp vector backends).

  • — (NaN cells — e.g. the cone of influence that spectrogram_cwt masks)

  • ``(ax (are left blank. Returns) –

  • im)``.

dasio.plot.plot_fk(F, f=None, k=None, ax=None, db=True, perc=100.0, vmin=None, vmax=None, scaling=None, cmap='turbo', cbar=True, velocities=None, figsize=None, **kwargs)[source]

Image the f-k plane from dasio.fk.fk. :param F: f-k amplitude from fk, or the whole (F, f, k) result. :param f: frequency axis [Hz]; taken from F when it is the whole result. :param k: wavenumber axis [rad/m]; likewise. :param ax: axes to draw on; a new figure is made when None. :param db: scale to dB re the panel maximum. :param perc: percentile for the upper colour limit. :param vmin: lower colour limit; defaults to 60 dB below vmax. :param vmax: upper colour limit. :param scaling: what the panel is, matching how fk produced it — the same

token in both places. None (default) is a raw complex amplitude: dB is taken relative to the panel maximum, because the absolute level of a raw transform has no meaning (it moves with the sample count), and the consequence is that two panels cannot be compared with each other. ‘density’ is what fk(..., scaling='density') returns: a power, so 10*log10 against one unit, absolute and comparable between panels.

Parameters:
  • cmap (str) – colormap name.

  • cbar (bool) – draw the colour bar.

  • velocities – c = 2*pi*f/k guide lines [m/s] — positive values draw both directions, signed values draw one.

  • figsize (Tuple[float, float] | None) – figure size when ax is None.

  • **kwargs – forwarded to pcolormesh.

  • db (bool)

  • perc (float)

  • vmin (float | None)

  • vmax (float | None)

  • scaling (str | None)

Returns:

the axes drawn on. im: the QuadMesh, for a colour bar or limit tweak.

Return type:

ax

Raises:

ValueError – if F is not 2-D or the axes do not match it.

Data first, like the other plotters here — either the whole result or the array plus its axes:

plot_fk(*fk(d.data, d.dx, d.dt)) plot_fk(F, f, k, velocities=[1500, 3000])

Frequency runs along x and wavenumber along y, so the apparent-velocity fans open sideways and a dispersive arrival reads left to right. db scales relative to the panel maximum, since an f-k amplitude inherits the gather’s units and only its contrast means anything.

dasio.plot.plot_fc(A, f=None, c=None, ax=None, db=False, perc=100.0, vmin=None, vmax=None, scaling=None, cmap='turbo', cbar=True, picks=None, figsize=None, **kwargs)[source]

Image the dispersion panel from dasio.fk.fk2fc. :param A: dispersion panel from fk2fc, or the whole (A, f, c) result. :param f: frequency axis [Hz]; taken from A when it is the whole result. :param c: velocity axis [m/s]; likewise. :param ax: axes to draw on; a new figure is made when None. :param db: scale to dB re the panel maximum. :param perc: percentile for the upper colour limit. :param vmin: lower colour limit. :param vmax: upper colour limit. :param scaling: what the panel is, as in plot_fk; pass ‘density’ for one

built from fk(..., scaling='density').

Parameters:
  • cmap (str) – colormap name.

  • cbar (bool) – draw the colour bar.

  • picks – (f, c) sequences to overlay, e.g. a picked dispersion curve.

  • figsize (Tuple[float, float] | None) – figure size when ax is None.

  • **kwargs – forwarded to pcolormesh.

  • db (bool)

  • perc (float)

  • vmin (float | None)

  • vmax (float | None)

  • scaling (str | None)

Returns:

the axes drawn on. im: the QuadMesh, for a colour bar or limit tweak.

Return type:

ax

Raises:
  • ValueError – if A is not 2-D or the axes do not match it.

  • plot_fc(*fk2fc(F, f, k, 200, 5000)) –

Frequency on x, apparent velocity on y — the orientation dispersion curves are picked in. Linear colour by default: a dispersion image is read by where its ridge is, and dB flattens the contrast that makes it stand out.

dasio.plot.plot_timeline(db, ax=None, by_month=None)[source]

Gantt-style coverage timeline for a DASdb.

Green where data exists, red for the acquisition gaps between.

With by_month=True each calendar month becomes its own row against a shared day-of-month axis, which keeps a multi-month archive legible — a year on one continuous axis compresses every gap into a hairline. None (default) picks that layout automatically when the catalog spans more than one month.

Bar geometry is computed in matplotlib date numbers rather than handed over as Timestamp/Timedelta objects. Mixing the two lets the unit machinery interpret a width in the same frame as a position, and a single bad conversion is enough to push the axes limits far enough that rendering a bbox_inches='tight' figure asks for a canvas of millions of pixels.

dasio.noise

Ambient-noise cross-correlation for DAS.

preprocess runs the standard CC preparation chain on a freshly loaded (continuous, unprocessed) DASdata — differentiate, detrend, band-pass, decimate, common-mode removal, temporal normalization — reusing the core dasio ops. temporal_normalization and spectral_whitening are the individual amplitude-flattening steps; xcorr is the FFT cross-correlation.

The channel-pair gathers (common_shot_pairs, common_offset_pairs) build the (ch1, ch2) index arrays that say which channels to correlate. xcorr_stack cross-correlates those pairs over the 60-s-style segments of one DASdata and stacks; xcorr_dataset walks a whole DASdb, preprocessing and stacking every segment. Both return a CCGather — the (npair, lag) stacked correlation plus its lag axis, with a .plot() method.

PyTorch is an optional dependency (pip install 'dasio[noise]'); it is imported lazily inside xcorr / spectral_whitening, so preprocess, temporal_normalization and the gather builders (all CPU) work without it.

dasio.noise.preprocess(d, *, differentiate=True, detrend=True, diff_method='central', freqmin=None, freqmax=None, taper_alpha=None, order=14, zerophase=True, decimate=1, subtract_common_mode=True, norm_window_sec=None)[source]

Standard ambient-noise CC preparation for a raw-loaded DASdata.

Runs the canonical chain on continuous data straight off the reader and returns a fresh DASdata: differentiate -> detrend -> (taper ->) band-pass -> decimate -> common-mode removal -> temporal normalization. Each step reuses an existing dasio op; compose them yourself for a non-standard recipe.

differentiatestrain -> strain rate (the usual CC quantity). Turn off if

the data is already a rate.

diff_method“central” (default, second-order central difference, bit-

identical to np.gradient) or “backward” (first-order, first sample zeroed). Only used when differentiate is True.

freqmin, freqmax : band-pass corners in Hz; skipped unless both are given. taper_alpha : Tukey edge-taper fraction applied just before the band-pass

(None skips it). Matches the legacy filter(data) = tukey * data -> butter recipe; e.g. 0.05 tapers 2.5 % at each end before filtering.

order, zerophaseButterworth band-pass order and zero-phase flag (order 4
  • zerophase=True reproduces a butter(4) + filtfilt pass).

decimateinteger time-decimation factor, applied as a plain stride AFTER

the band-pass — the band-pass is the anti-alias filter, so choose freqmax < fs / (2 * decimate). 1 (default) means no decimation.

norm_window_sectemporal normalization. None skips it; 0 = one-bit;

>0 = running-absolute-mean window in seconds.

detrend and common-mode removal are always applied — they are part of the standard recipe. CPU only (no PyTorch needed; that’s just xcorr).

Parameters:
  • d (DASdata)

  • differentiate (bool)

  • detrend (bool)

  • diff_method (str)

  • freqmin (float | None)

  • freqmax (float | None)

  • taper_alpha (float | None)

  • order (int)

  • zerophase (bool)

  • decimate (int)

  • subtract_common_mode (bool)

  • norm_window_sec (float | None)

Return type:

DASdata

dasio.noise.temporal_normalization(d, window_sec=None)[source]

Per-channel temporal amplitude normalization.

window_sec None or 0 -> one-bit (the sign of each sample). Otherwise running-absolute-mean: divide each sample by a moving average of |amplitude| over window_sec seconds (a common choice is ~half the longest period of interest). Flattens the amplitude envelope so transients don’t dominate the cross-correlation. Returns a fresh DASdata.

Parameters:
  • d (DASdata)

  • window_sec (float | None)

Return type:

DASdata

dasio.noise.spectral_whitening(spec, smooth_bins=1, eps=0.001, band=None, df=None)[source]

Flatten a spectrum’s amplitude while keeping its phase (PyTorch).

smooth_bins == 1 is phase-only whitening: divide by |spec| in every bin. smooth_bins > 1 is running-absolute-mean (RAM) whitening: divide by a moving average of |spec| over that many frequency bins, which keeps the broad spectral shape that phase-only erases. The denominator is floored at eps x the row’s mean magnitude so empty (out-of-band) bins stay ~0 instead of blowing up.

bandoptional (f1, f2) Hz pass-band. After whitening, bins below

f1 and above f2 are smoothly suppressed with a cos**2 edge taper (1 at the band edge, 0 at DC / Nyquist), reproducing the legacy spectral_whitening band-limit so whitening doesn’t re-flatten out-of-band noise. Needs df (the rFFT bin spacing, fs / nfft).

spec is a torch rFFT tensor (last axis = frequency); returns a tensor.

dasio.noise.xcorr(a, b, *, whiten=False, fs=None, max_lag=None, eps=0.001, whiten_band=None, device=None)[source]

FFT cross-correlation along the time axis (PyTorch).

Correlates a and b per channel. a is (nch, npts); b is either the same shape (channel-wise) or a single (npts,) trace, broadcast against every channel of a (a virtual-source gather). Linear (zero-padded, not circular) correlation; the lag axis is centered, so column max_lag — or npts-1 when max_lag is None — is zero lag.

whitenspectral whitening before correlating. False -> none; True

-> phase-only; a float -> running-absolute-mean whitening with that smoothing bandwidth in Hz (requires fs). See spectral_whitening. Assumes inputs are already band-limited (bandpass first).

fssampling rate, needed when whiten is a Hz bandwidth or whiten_band

is set.

whiten_bandoptional (f1, f2) Hz pass-band for the whitening (needs

fs); applies the legacy cos**2 band-limit so whitening doesn’t re-flatten out-of-band noise. See spectral_whitening.

max_lagkeep only +/- this many lag samples (returns 2*max_lag+1);

None returns the full 2*npts-1.

eps : whitening floor, as a fraction of each row’s mean magnitude. device : torch device for the FFTs (e.g. 'cuda'); None uses the inputs’.

Returns a numpy array (nch, 2*npts-1) or (nch, 2*max_lag+1).

dasio.noise.haversine_km(lat1, lon1, lat2, lon2)[source]

Great-circle distance in km between two lon/lat points (vectorized).

A lightweight stand-in for obspy.geodetics — spherical-earth haversine, accurate to a few tenths of a percent, which is plenty for picking common-offset channel pairs. All four arguments broadcast, so you can pass one point against an array of channels (haversine_km(lat[i], lon[i], lat, lon)) to get that channel’s distance to every other.

dasio.noise.common_shot_pairs(source, channels)[source]

Pair one source channel against every channel — a common-shot gather.

source is the fixed source channel index. channels is the list of receiver indices, or an int n meaning range(n). Returns (ch1, ch2) integer arrays with ch1 filled with source, suitable for xcorr_stack / xcorr_dataset.

dasio.noise.common_offset_pairs(lat, lon, offset_km, tol_km=None)[source]

Channel pairs separated by ~`offset_km` — a common-offset gather.

For each channel i, find the forward channel j > i whose great-circle distance to i is closest to offset_km (via haversine_km), and keep the pair when that distance is within tol_km. Forward-only search keeps a consistent pair orientation and avoids duplicate (i, j)/(j, i). lat/lon are per-channel coordinate arrays (degrees).

tol_km None keeps every channel’s nearest-to-offset partner — including channels near the far end that have no true match; pass a tolerance (e.g. half the channel spacing) to drop those. Returns (ch1, ch2).

class dasio.noise.CCGather(cc, lag_s, fs, nseg)[source]

A stacked cross-correlation gather — what xcorr_stack / xcorr_dataset return.

cc is the (npair, nlag) mean cross-correlation over nseg stacked segments, zero lag at the centre column; lag_s is the matching lag axis in seconds and fs the (post-preprocess) sample rate. plot() images it via dasio.plot.plot_xcorr — the same DASdata-style “the object knows how to draw itself” convenience, so callers don’t juggle cc and lag_s separately.

Parameters:
  • cc (ndarray)

  • lag_s (ndarray)

  • fs (float)

  • nseg (int)

plot(**kwargs)[source]

Image the gather (lag vs pair); see dasio.plot.plot_xcorr.

dasio.noise.xcorr_stack(d, ch1, ch2, *, seg_sec, max_lag_sec, whiten=False, whiten_band=None, eps=0.001, chunk=1250, device=None)[source]

Cross-correlate channel pairs over seg_sec segments and stack.

Splits d into back-to-back windows of seg_sec seconds (the trailing remainder is dropped), cross-correlates each pair’s matching segments with xcorr, and averages over the segments — the segment stack. ch1/ch2 are equal-length channel-index arrays (e.g. from common_shot_pairs); they index the channel axis of d.data.

seg_sec, max_lag_secsegment length and kept lag half-width, in seconds;

converted to samples with d.fs, so this is decimation-agnostic.

whiten, whiten_bandforwarded to xcorr (whitening mode and optional

(f1, f2) Hz band-limit).

chunk : process this many pairs per xcorr call to bound memory.

Returns a CCGather (mean cross-correlation over its nseg segments), or None when d is shorter than one segment. xcorr_dataset weights these by nseg to accumulate across windows.

Parameters:
  • d (DASdata)

  • seg_sec (float)

  • max_lag_sec (float)

  • eps (float)

  • chunk (int)

dasio.noise.xcorr_dataset(db, ch1, ch2, *, max_lag_sec, seg_sec=60.0, begin=None, end=None, window_sec=3600.0, min_ch=0, max_ch=None, prep=None, whiten=False, whiten_band=None, eps=0.001, chunk=1250, device=None)[source]

Cross-correlate channel pairs across a whole DASdb and stack.

Walks the catalog’s time-contiguous segments, reads each segment in continuous window_sec chunks (DASdb.read), runs preprocess on each chunk, then xcorr_stack, accumulating the per-segment sums. The final result is the mean cross-correlation over every segment of every chunk — the dataset-level stack.

db : a DASdb catalog of the raw files to correlate. ch1, ch2 : channel-index pair arrays (e.g. from common_shot_pairs /

common_offset_pairs); they index the read range [min_ch, max_ch) (default = all channels, so indices are absolute). Channel selection is the caller’s job — restrict the read with min_ch/max_ch and only reference good channels in ch1/ch2 (bad channels read but unpaired).

min_ch, max_ch : contiguous channel range to read each window. begin, end : tz-aware UTC datetimes bounding the run (default = full

catalog span). Group by day by calling once per day’s [begin, end).

window_seccontinuous read-chunk length; a multiple of seg_sec wastes no

samples at chunk boundaries. Iterating per segment means a segment never straddles an acquisition gap.

prepkwargs forwarded to preprocess (e.g. ``dict(freqmin=0.1,

freqmax=20, decimate=5, norm_window_sec=0)``); {} runs the preprocess defaults.

whiten, whiten_band, eps, chunk, deviceforwarded down to xcorr via

xcorr_stack.

Returns a CCGather whose cc is the mean cross-correlation over the nseg segments of every window. Raises if no data falls in the requested range. Assumes a uniform sample rate across the catalog.

Parameters:
  • max_lag_sec (float)

  • seg_sec (float)

  • window_sec (float)

  • min_ch (int)

  • max_ch (int | None)

  • prep (dict | None)

  • eps (float)

  • chunk (int)

dasio.picking

PhaseNet-DAS P/S phase picking. phasenet (AI4EPS) and torch are optional and imported lazily: pip install 'dasio[pick]'.

class dasio.picking.Picks(df: object, fs: float, model: str, begin_time: datetime.datetime | None, nx: int, nt: int, t0_sec: float = 0.0, scores: numpy.ndarray | None = None)[source]
Parameters:
  • df (object)

  • fs (float)

  • model (str)

  • begin_time (datetime | None)

  • nx (int)

  • nt (int)

  • t0_sec (float)

  • scores (ndarray | None)

plot(ax=None, s=4, **kwargs)[source]

Scatter P (red) / S (blue) picks — channel (x) vs time (y, downward).

dasio.picking.pick_phases(d, model='phasenet-das+', *, min_prob=0.3, device=None, return_scores=False, nx_win=2048, nt_win=4096, min_t_overlap=0.1, taper_nx=True, taper_nt=True)[source]

Pick P/S phases with PhaseNet-DAS. Feeds d.data verbatim (model self-normalizes).

dasio.synthetic

A synthetic record, so a snippet or a notebook has something to run on.

Built in the order the physics happens: a wavefield along the fibre, differentiated into strain, then what the interrogator adds on top. Strain, not strain rate: that is what the OptaSense archive stores, and differentiate() is one call away.

dasio.synthetic.strain(w)[source]

Displacement along the fibre to the strain the fibre feels.

dasio.synthetic.boussinesq_strain(x, y, z, load, shear=80000000.0, nu=0.3)[source]

Strain along x at (x, y, z) below a vertical point load on the surface of a half-space: d/dx of Boussinesq’s horizontal displacement. Checked against the numerical derivative to 1e-6.

dasio.synthetic.scatter_coda(x, src, t0, fs, nt, vp, vs, fc, rng, a=40.0, kappa=0.3, cell=25.0, v_slow=500.0, q=150.0)[source]

Coda by single (Born) scattering off a random near-surface medium.

The medium is a velocity perturbation on a grid with a von Karman autocorrelation: length a, Hurst exponent kappa. Its power spectrum, (1 + k^2 a^2)^-(kappa+1), is what sets how strongly each wavelength scatters, and the power-law tail is why the crust scatters at every scale where a Gaussian medium would go quiet. Every cell scatters the incident wave once – no multiple scattering, which is what Born means.

Every arrival is the same wavelet at a different delay, so the delays go into a spike train and one convolution serves every channel: cells x nx x nt wavelet evaluations become cells x nx additions and one FFT.

dasio.synthetic.demo_record(nx=300, nt=6000, fs=100.0, dx=10.0, seed=0)[source]

A roadside fibre: a local earthquake with its coda, three vehicles, and the noise a real interrogator writes underneath.

The defaults are a compromise the signals force. P and S only separate if the source is far enough away (2.4 km: four wavelet periods apart), the hyperbolas only curve if the array is long enough to see it, and a car at 25 m/s needs a minute to cross any of that – so 3 km of fibre and 60 s.

Amplitudes are physical: peak S strain for the quake, axle weights on a road bed for the cars, an rms for the noise, all in microstrain.

Return type:

DASdata