import numpy as np
import dascore as dc
patch = dc.get_example_patch()Patch
A Patch couples an array with coordinate labels and metadata. Patch methods return new patches instead of modifying the original.
Creating patches
Examples and files
DASCore includes example patches for tutorials and tests:
event = dc.get_example_patch("example_event_1")Use dc.spool(...) to open DAS files and take a patch by indexing the spool:
file_patch = dc.spool("examples://terra15_das_1_trimmed.hdf5")[0]An examples:// name refers to a file in DASCore’s example data registry; it is downloaded and cached the first time it is used. Anywhere it appears in these docs, a path to your own data works just as well. Generated examples, which have no file behind them, come from dc.get_example_patch and dc.get_example_spool instead.
See the spool tutorial for directories, archives, and remote resources.
Arrays
To build a patch directly, supply the data, its dimension names, and coordinates. Attributes are optional.
rng = np.random.default_rng(13)
data = rng.random((300, 2_000))
time = dc.to_datetime64("2017-09-18") + np.arange(2_000) * dc.to_timedelta64(1 / 250)
distance = np.arange(300)
manual_patch = dc.Patch(
data=data,
dims=("distance", "time"),
coords={"distance": distance, "time": time},
attrs={"data_units": "um/(m * s)"},
)Anatomy
Data and coordinates
Patch.data is an immutable n-dimensional array and must be treated as such; it generally cannot be changed in place. Copy it before changing values directly.
data_copy = np.array(patch.data)
data_copy[:10] = 0Patch.coords manages dimension names and coordinates. The common accessors are available on the patch itself:
time_coord = patch.get_coord("time")
time_values = patch.get_array("time")
distance_max = patch.coords.max("distance")
time_step = patch.coords.step("time")A coordinate may be associated with one or more dimensions, such as latitude along distance, or be independent of the data. See the advanced coordinates tutorial for direct BaseCoord and CoordManager use.
Dimension coordinates usually share a dimension’s name, but that is not required. Auxiliary coordinates such as latitude commonly depend on distance; a quality grid may depend on both distance and time; deployment metadata can be independent of every dimension.
String coordinates support exact matches, wildcards, compiled regular expressions, masks, and sorting, but not units or numeric ranges. See String coordinates for examples and literal-label caveats.
Attributes and summaries
Patch.attrs contains validated, non-coordinate metadata such as data_units, acquisition_key, gauge_length, and processing history. Coordinate bounds and steps are derived through Patch.summary rather than duplicated in attrs.
print(patch.attrs.data_units)
print(patch.summary.get_coord_summary("time").min)
row = patch.flat_dump()
print(row["time_min"], row["distance_step"])None
2017-09-18T00:00:00.000000000
2017-09-18T00:00:00.000000000 1
Patch.summary is read-only. flat_dump() is intended for tabular rows and produces the same coordinate-summary column names used by spool indexes, including time_min, time_max, and distance_step. The optional data_type attribute is a display hint; interpret data through its units, coordinates, and history.
A preview of the validated attribute schema is available programmatically:
attribute_fields = dc.PatchAttrs.get_summary_df()
attribute_fields.head()| description | |
|---|---|
| attribute | |
| data_type | Describes the quantity being measured. |
| data_category | Describes the type of data. |
| data_units | The units of the data measurements |
| acquisition_key | Inventory identity of the data source, spelled... |
| tag | A custom string field. |
Patch functions append to attrs.history. Disable new entries temporarily when provenance is not needed:
from dascore.config import config_context
with config_context(patch_history="disabled"):
without_new_history = patch.abs()Existing history remains intact while recording is disabled. This is useful for tight processing loops where a full operation log is unnecessary; leave the default enabled when results must be audited later.
Patch identity
Two attributes track identity:
| Attribute | Meaning | Changes when |
|---|---|---|
patch_id |
Which source data this is | Multiple sources are combined |
processing_id |
Which operations produced it | A patch function changes the patch |
filtered = patch.pass_filter(time=(1, 10))
assert filtered.attrs.patch_id == patch.attrs.patch_id
assert filtered.attrs.processing_id != patch.attrs.processing_idThe processing route matters. Repeating the same operations produces the same processing_id, while changing their order does not:
first = patch.pass_filter(time=(1, 10)).decimate(time=2)
same = patch.pass_filter(time=(1, 10)).decimate(time=2)
other = patch.decimate(time=2).pass_filter(time=(1, 10))
assert first.attrs.processing_id == same.attrs.processing_id
assert first.attrs.processing_id != other.attrs.processing_idWhen a file does not contain a patch_id, DASCore derives one from the format, version, path, patch key, file size, and modification time. Repeated reads and metadata scans therefore agree without hashing the data array. Copying the same bytes to another path produces a different derived ID, and overwriting a path also changes it. Formats that support stored lineage IDs restore those values when the file is read elsewhere.
In-memory patches receive process-local IDs. Direct calls to new, update, and update_attrs preserve both IDs because patch functions use them to assemble results. Changing data through these low-level methods does not describe a processing step:
doubled = patch.new(data=patch.data * 2)
assert doubled.attrs.patch_id == patch.attrs.patch_id
assert doubled.attrs.processing_id == patch.attrs.processing_idUse patch functions when processing should advance provenance. A function that returns the input unchanged advances neither ID, and IDs do not participate in Patch.equals. Set patch_provenance="disabled" only when IDs are not needed. See Finding a patch by its ID and Workflow for lookup and operation fingerprints.
Display and shortcuts
Displaying a patch summarizes its data, coordinates, attributes, and history. HTML displays use collapsible sections; display_html selects the text representation instead.
patchCoordinates (distance: 300, time: 2000)
| kind | min | max | span | step | shape | dtype | |
|---|---|---|---|---|---|---|---|
| *distance | CoordRange | 0 m | 299 m | <299 m> | 1 m | (300,) | int64 |
| *time | CoordRange | 2017-09-18T00:00:00 | …:07.996 | <8 s> | 0.004s · 250 Hz | (2000,) | datetime64[ns] |
Data (float64, 4.6 MiB)
[[0.778 0.238 0.824 ... 0.37 0.077 0.232]
[0.497 0.442 0.703 ... 0.126 0.118 0.78 ]
[0.207 0.195 0.174 ... 0.849 0.365 0.807]
...
[0.619 0.105 0.669 ... 0.621 0.436 0.5 ]
[0.757 0.259 0.091 ... 0.361 0.937 0.104]
[0.158 0.295 0.585 ... 0.229 0.24 0.494]]Attributes
tag: random category: DAS
For time-distance patches, seconds and channel_count provide quick size information. get_patch_name() returns the default filename for a patch.
print(patch.seconds, patch.channel_count)
print(patch.get_patch_name())8.0 300
DAS_____random__2017_09_18__2017_09_18T00_00_07
Spool.get_patch_names() applies the same naming convention to every managed patch. Display precision, truncation, and folding are controlled by the runtime configuration.
Selecting and reshaping
Select
Patch.select keeps coordinate values inside a range without reordering them. Use ... or None for an open bound.
time = patch.get_coord("time")
one_second = dc.to_timedelta64(1)
trimmed = patch.select(time=(time.min() + one_second, time.max() - one_second))
first_half = patch.select(distance=(..., np.mean(patch.get_array("distance"))))With relative=True, positive bounds are measured from the start and negative bounds from the end:
trimmed = patch.select(time=(1, -1), relative=True)Relative quantities are converted before measuring from an endpoint:
from dascore.units import ft
last_hundred_feet = patch.select(distance=(-100 * ft, ...), relative=True)With samples=True, values are array indices. Sample ranges follow Python slicing, while coordinate-value ranges include both endpoints.
| Query | Result |
|---|---|
select(distance=(0, 10)) |
Coordinate values 0 through 10, inclusive |
select(time=(0, 10), samples=True) |
Samples 0 through 9 |
select(time=(0, -1), samples=True) |
Every sample except the last |
select(time=-1, samples=True) |
Only the last sample |
first_ten = patch.select(time=slice(0, 10), samples=True)
last_channel = patch.select(distance=-1, samples=True)Arrays select a set of coordinate values or sample positions:
chosen = patch.select(distance=np.array([10, 18, 12]))
chosen_samples = patch.select(distance=np.array([0, 12, 10, 9]), samples=True)Multiple keywords apply one selection per coordinate in the same call. The resulting array is the intersection of those selections:
window = patch.select(
time=(1, -1),
distance=(0, 100),
relative=True,
)select and order accept slices as range shorthand; sample slices still require samples=True. Processing functions such as pass_filter and taper require tuples.
Unselect and order
Patch.unselect removes what select would keep. Removing an interior range creates a segmented coordinate.
outside = patch.unselect(distance=(50, 200))
assert outside.get_coord("distance").step is NoneEach named coordinate is complemented independently. At spool level, patch coordinates cannot be unselected because removing an interior interval could turn one patch into two; use Patch.unselect when subdivision is intended.
Patch.order instead follows the supplied order and may duplicate samples.
ordered = patch.order(distance=np.array([20, 10, 20]))Patch.append_dims adds a dimension and optionally repeats the data along it:
expanded = patch.append_dims(realization=2).update_coords(realization=[0, 1])The new dimension is appended to the array. Use transpose to place it elsewhere:
expanded = expanded.transpose("distance", "realization", "time")Processing and visualization
Patch methods form a fluent interface, so processing steps can be chained:
processed = (
patch.decimate(time=8)
.detrend(dim="distance")
.pass_filter(time=(..., 10))
)See Patch processing and the dascore.proc API for available operations.
Plots are available through Patch.viz:
event.taper(time=0.05).pass_filter(time=(..., 300)).viz.waterfall(show=True);
See the visualization tutorial for plot options.
Updating patches
Patch.update replaces data, coordinates, or the complete attribute model. Patch.update_attrs changes selected metadata fields.
scaled = patch.update(data=patch.data * 10)
retagged = patch.update_attrs(tag="processed")Patch.update_coords modifies or adds coordinates. Tuple values specify (associated_dimensions, values).
shifted = patch.update_coords(time_min=patch.coords.min("time") + one_second)
latitude = np.linspace(41.5, 41.8, patch.shape[0])
located = patch.update_coords(latitude=("distance", latitude))
quality = np.ones_like(patch.data)
with_quality = located.update_coords(quality=(patch.dims, quality))Several coordinates can be attached in one call, and the associated dimension determines the required shape:
longitude = np.linspace(-109.9, -109.6, patch.shape[0])
located = patch.update_coords(
latitude=("distance", latitude),
longitude=("distance", longitude),
)The same tuple syntax works during construction:
rebuilt = dc.Patch(
data=patch.data,
dims=patch.dims,
coords={
"distance": patch.get_array("distance"),
"time": patch.get_array("time"),
"latitude": ("distance", latitude),
"quality": (patch.dims, quality),
},
attrs=patch.attrs,
)To change an association, pass the same values with a new dimension tuple. This changes coordinate metadata without reshaping the patch.
Coordinates not associated with a dimension use (None, values). Patch.drop_coords removes non-dimensional coordinates; dimension coordinates cannot be dropped.
detached = located.update_coords(latitude=(None, latitude))
without_latitude = detached.drop_coords("latitude")Units
Patch.set_units changes unit labels without converting values. Patch.convert_units converts the data or coordinate values.
with_units = patch.set_units("m/s", distance="ft")
converted = with_units.convert_units("ft/s", distance="m")Use set_units only when values are already expressed in the new unit or when correcting a missing label. Use convert_units when numeric values must preserve the same physical quantity. Data units are stored as Pint quantities and can be formatted with get_quantity_str.
Quantities may be passed directly to selection and processing functions:
from dascore.units import ft, m
selected = patch.select(distance=(10 * ft, 10 * m))
filtered = patch.pass_filter(distance=(10 * m, 100 * m))Quantities are converted to the coordinate’s units before selection or processing, so the same expression works after a coordinate is converted from metres to feet.
Arithmetic and NumPy
Scalar and NumPy-array operations broadcast over patch data. Patch-to-patch operations require compatible metadata and align coordinates on their intersection; units participate in the calculation. See Patch compatibility for the shared rules.
scaled = patch / 10
offset = patch + np.ones(patch.shape)
combined = patch + patch
assert np.allclose(scaled.data, patch.data / 10)
assert np.allclose(combined.data, patch.data * 2)Operations between patches first reject conflicting values in the configured patch_kind_attrs, then align shared coordinate values. Broadcast-compatible shapes are supported:
one_channel = patch.select(distance=0, samples=True).squeeze()
broadcast_sum = patch + one_channel
assert broadcast_sum.shape == patch.shapeQuantities update data_units as part of the arithmetic:
from dascore.units import s
velocity = patch.set_units("m/s")
displacement = velocity * (2 * s)
print(displacement.attrs.data_units)1 m
Array operands must broadcast against patch data. Coordinate-aware alignment occurs only for another Patch; a bare NumPy array contributes no coordinate or metadata information.
NumPy ufuncs operate directly on patches, and DASCore patch ufuncs support named-dimension reduction and accumulation.
absolute = np.abs(patch)
cumulative = patch.add.accumulate("time")
summed = patch.add.reduce("distance")Patch.apply_ufunc also accepts a NumPy ufunc explicitly. DASCore maps named dimensions to NumPy axes and preserves compatible coordinates:
same_absolute = patch.apply_ufunc(np.abs)
scaled = patch.apply_ufunc(np.multiply, 10)Named reduction removes the reduced dimension and coordinates which depend on it. Accumulation retains the original shape and dimension coordinates.