Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions dascore/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ def map(self, func, iterables, **kwargs):
"temperature",
"temperature_gradient",
"brillouin_spectrum",
"fourier_transform",
"amplitude_spectrum",
"power_spectrum",
"power_spectral_density",
"frequency_band_energy",
"stalta",
"kurtosis",
"envelope",
"correlation",
"tau_p",
"dispersion",
"phase_weighted_stack",
)

# Valid categories (of instruments)
Expand All @@ -87,7 +99,7 @@ def map(self, func, iterables, **kwargs):
"file_version": 9,
"experiment_id": 50,
"instrument_id": 50,
"data_type": 20,
"data_type": 32,
"data_category": 4,
}

Expand Down Expand Up @@ -216,13 +228,18 @@ def map(self, func, iterables, **kwargs):


DEFAULT_COLORMAPS = {
"frequency-band energy": "Spectral_r",
"frequency_band_energy": "Spectral_r",
"stalta": "RdGy_r",
"kurtosis": "gnuplot2",
"fourier transform": "magma",
"power spectral density": "turbo",
"power spectrum": "turbo",
"amplitude spectrum": "turbo",
"envelope": "viridis",
"correlation": "RdBu_r",
"tau_p": "magma",
"dispersion": "turbo",
"phase_weighted_stack": "viridis",
"fourier_transform": "magma",
"power_spectral_density": "turbo",
"power_spectrum": "turbo",
"amplitude_spectrum": "turbo",
"strain_rate": "RdBu_r",
"strain": "seismic",
"velocity": "viridis",
Expand Down
4 changes: 2 additions & 2 deletions dascore/proc/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def sum(
return aggregate.func(patch, dim=dim, method=np.nansum, dim_reduce=dim_reduce)


@patch_function()
@patch_function(data_type="")
@compose_docstring(params=AGG_DOC_STR, notes=AGG_NOTES)
def any(
patch: PatchType,
Expand All @@ -279,7 +279,7 @@ def any(
return aggregate.func(patch, dim=dim, method=np.any, dim_reduce=dim_reduce)


@patch_function()
@patch_function(data_type="")
@compose_docstring(params=AGG_DOC_STR, notes=AGG_NOTES)
def all(
patch: PatchType,
Expand Down
8 changes: 4 additions & 4 deletions dascore/proc/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def imag(patch: PatchType) -> PatchType:
return patch.new(data=np.imag(patch.data))


@patch_function()
@patch_function(data_type="")
def angle(patch: PatchType) -> PatchType:
"""
Return a new patch with the phase angles from the data array.
Expand All @@ -325,7 +325,7 @@ def angle(patch: PatchType) -> PatchType:
return patch.new(data=np.angle(patch.data))


@patch_function()
@patch_function(data_type="")
def normalize(
self: PatchType,
dim: str,
Expand Down Expand Up @@ -387,7 +387,7 @@ def normalize(
return self.new(data=new_data)


@patch_function()
@patch_function(data_type="")
def standardize(
self: PatchType,
dim: str,
Expand Down Expand Up @@ -793,7 +793,7 @@ def flip(patch, *dims, flip_coords=True):
return patch.new(data=data, coords=coords)


@patch_function()
@patch_function(data_type="")
def full(patch, fill_value):
"""
Return an identical patch with the data replaced by fill_value.
Expand Down
4 changes: 2 additions & 2 deletions dascore/proc/correlate.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def _get_source_fft(patch, dim, source, source_axis, samples):
return out


@patch_function()
@patch_function(data_type="correlation")
def correlate_shift(
patch: PatchType, dim: str, undo_weighting: bool = True
) -> PatchType:
Expand Down Expand Up @@ -86,7 +86,7 @@ def correlate_shift(
return out


@patch_function()
@patch_function(data_type="correlation")
def correlate(
patch: PatchType,
samples: bool = False,
Expand Down
2 changes: 1 addition & 1 deletion dascore/transform/differentiate.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _strided_diff(order, patch, axes, dx_or_spacing, step):
return new_data


@patch_function()
@patch_function(data_type="")
def differentiate(
patch: PatchType,
dim: str | Sequence[str] | None,
Expand Down
2 changes: 1 addition & 1 deletion dascore/transform/dispersion.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from dascore.utils.patch import patch_function


@patch_function(required_dims=("time", "distance"))
@patch_function(required_dims=("time", "distance"), data_type="dispersion")
def dispersion_phase_shift(
patch: PatchType,
phase_velocities: Sequence[float],
Expand Down
4 changes: 2 additions & 2 deletions dascore/transform/fbe.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ def fbe(
patch = patch.pass_filter(**kwargs)

fbe = ((patch**2).rolling(**{dim: window, "step": step}).mean() ** 0.5).update(
attrs={"data_type": "Frequency-Band Energy"}
attrs={"data_type": "frequency_band_energy"}
)

if db:
fbe = (10 * fbe.log10()).update(
attrs={"data_type": "Frequency-Band Energy", "data_units": "dB"}
attrs={"data_type": "frequency_band_energy", "data_units": "dB"}
)

return fbe
2 changes: 1 addition & 1 deletion dascore/transform/fft.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from dascore.utils.transformatter import FourierTransformatter


@patch_function()
@patch_function(data_type="fourier_transform")
@deprecate(
info="The Patch transform rfft is deprecated. Use dft instead.",
removed_in="0.2.0",
Expand Down
16 changes: 10 additions & 6 deletions dascore/transform/fourier.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
from dascore.utils.transformatter import FourierTransformatter

DFT_OUTPUT_DATA_TYPE_MAP = {
"AS": "Amplitude Spectrum",
"PS": "Power Spectrum",
"PSD": "Spectral Density",
"AS": "amplitude_spectrum",
"PS": "power_spectrum",
"PSD": "power_spectral_density",
}
DFT_OUTPUT_TYPES = ("FFT", *DFT_OUTPUT_DATA_TYPE_MAP)

Expand Down Expand Up @@ -127,7 +127,7 @@ def _get_dft_attrs(patch, dims, new_coords, pad=False, output="FFT"):
new["dims"] = new_coords.dims
new["data_units"] = _get_dft_data_units(patch, dims)
new["_pre_dft_data_type"] = new.get("data_type")
new["data_type"] = "fourier transform"
new["data_type"] = "fourier_transform"
new["_dft_output"] = output
new["_dft_padded"] = pad
return PatchAttrs(**new)
Expand Down Expand Up @@ -498,7 +498,7 @@ def _get_stft_dims(dim, dims, axis):
return out


@patch_function()
@patch_function(data_type="fourier_transform")
def stft(
patch: PatchType,
taper_window: str | ndarray | tuple[str | Any, ...] = "hann",
Expand Down Expand Up @@ -606,6 +606,7 @@ def stft(
"_stft_fft_mode": fft_mode,
"_stft_mfft": window_samples,
"_stft_performed": True,
"_pre_stft_data_type": patch.attrs.get("data_type"),
"data_units": _get_data_units_from_dims(patch, dim, mul),
}
attrs = patch.attrs.drop("coords").update(**new_attrs)
Expand Down Expand Up @@ -701,7 +702,10 @@ def istft(patch) -> PatchType:
new_data = data_untrimmed[index]
assert new_data.shape == cm.shape
# Re-assemble and return new patch.
new_attrs = {i: v for i, v in patch.attrs.items() if not i.startswith("_stft")}
patch_attrs = dict(patch.attrs)
new_attrs = {i: v for i, v in patch_attrs.items() if not i.startswith("_stft")}
if "_pre_stft_data_type" in patch_attrs:
new_attrs["data_type"] = new_attrs.pop("_pre_stft_data_type")
dim = patch.dims[time_axis]
new_attrs["data_units"] = _get_data_units_from_dims(patch, dim, truediv)
attrs = dc.PatchAttrs(**new_attrs).drop("coords")
Expand Down
6 changes: 3 additions & 3 deletions dascore/transform/hilbert.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from dascore.utils.patch import patch_function


@patch_function()
@patch_function(data_type="")
def hilbert(patch: PatchType, dim: str) -> PatchType:
"""
Perform a Hilbert transform on a patch.
Expand Down Expand Up @@ -57,7 +57,7 @@ def hilbert(patch: PatchType, dim: str) -> PatchType:
return patch.new(data=analytic_signal)


@patch_function()
@patch_function(data_type="envelope")
def envelope(patch: PatchType, dim: str) -> PatchType:
"""
Calculate the envelope of a signal using the Hilbert transform.
Expand Down Expand Up @@ -112,7 +112,7 @@ def __infer_transform_dim(patch, stack_dim):
return next(iter(dims))


@patch_function()
@patch_function(data_type="phase_weighted_stack")
@compose_docstring(dim_reduce=DIM_REDUCE_DOCS)
def phase_weighted_stack(
patch: PatchType,
Expand Down
2 changes: 1 addition & 1 deletion dascore/transform/integrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def _get_indefinite_integral(patch, dxs_or_vals, axes):
return out, patch.coords # coords shouldn't change


@patch_function()
@patch_function(data_type="")
def integrate(
patch: PatchType,
dim: Sequence[str] | str | None,
Expand Down
4 changes: 2 additions & 2 deletions dascore/transform/kurtosis.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def _validate_window(winlen: float, step: float) -> int:
"""Convert window length in seconds to samples and validate."""
if winlen <= 0:
raise ValueError("winlen must be positive.")
nwin = int(round(winlen / step))
nwin = round(winlen / step)
if nwin < 2:
raise ValueError("winlen is too small for the sampling interval.")
return nwin
Expand Down Expand Up @@ -230,5 +230,5 @@ def kurtosis(
return (
patch_t.new(data=out)
.transpose(*orig_dims)
.update(attrs={"data_type": "Kurtosis", "data_units": ""})
.update(attrs={"data_type": "kurtosis", "data_units": ""})
)
2 changes: 1 addition & 1 deletion dascore/transform/spectro.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def _get_new_dims(patch, dim, new_coord_name):
return tuple([*dims, dim])


@patch_function()
@patch_function(data_type="fourier_transform")
@deprecate(
info="Use Patch.stft() instead.",
since="0.1.11",
Expand Down
2 changes: 1 addition & 1 deletion dascore/transform/stalta.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ def stalta(
sta_data = patch.rolling(**{dim: sta}).mean()
lta_data = patch.rolling(**{dim: lta}).mean()

return (sta_data / lta_data).update(attrs={"data_type": "STALTA", "data_units": ""})
return (sta_data / lta_data).update(attrs={"data_type": "stalta", "data_units": ""})
2 changes: 1 addition & 1 deletion dascore/transform/taup.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def _jit_taup_general(data, distance, dt, p_vals):
return two_sided_p_vals, taup


@patch_function(required_dims=("time", "distance"))
@patch_function(required_dims=("time", "distance"), data_type="tau_p")
def tau_p(
patch: PatchType,
velocities: NDArray[np.floating],
Expand Down
22 changes: 21 additions & 1 deletion dascore/utils/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def patch_function(
required_attrs: attr_type = None,
history: Literal["full", "method_name", None] = "full",
validate_call: bool = False,
data_type: str | None = None,
):
"""
Decorator to mark a function as a patch method.
Expand All @@ -208,6 +209,10 @@ def patch_function(
If True, use pydantic to validate the function call. This can save
quite a lot of code in validation checks, but does have some overhead.
See [validate_call](https://docs.pydantic.dev/latest/api/validate_call/).
data_type
Controls the output patch's ``data_type`` attr. If None, leave the
returned patch's ``data_type`` unchanged. Otherwise, set to specified
value. Use an empty string ("") to clear.

Examples
--------
Expand Down Expand Up @@ -236,6 +241,16 @@ def patch_function(
... option: Literal["min", "max", None] = None,
... ):
... ...
>>>
>>> # 4. A patch method which sets the output data_type.
>>> @dc.patch_function(data_type="strain_rate")
... def do_strain_rate(patch):
... ...
>>>
>>> # 5. A patch method which clears the output data_type.
>>> @dc.patch_function(data_type="")
... def do_unknown_quantity(patch):
... ...

Notes
-----
Expand Down Expand Up @@ -263,6 +278,9 @@ def _func(patch, *args, **kwargs):
)
check_patch_attrs(patch, required_attrs)
out: PatchType = func(patch, *args, **kwargs)
attr_updates = {}
if data_type is not None:
attr_updates["data_type"] = data_type
# attach history string. Need to consider something a bit less hacky.
if out is not patch and hasattr(out, "attrs"):
hist_str = _get_history_str(
Expand All @@ -271,7 +289,9 @@ def _func(patch, *args, **kwargs):
if hist_str:
hist = list(out.attrs.history)
hist.append(hist_str)
out = out.update_attrs(history=hist)
attr_updates["history"] = hist
if attr_updates and hasattr(out, "attrs"):
out = out.update_attrs(**attr_updates)
return out

# Attach original function. Although we want to encourage raw_function
Expand Down
5 changes: 5 additions & 0 deletions docs/notes/notes.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ title: Notes
---

This section of the documentation provides understanding-oriented explanation for DASCore implementation and design decisions.

- [PatchAttrs](patch_attrs.qmd)
- [Documentation Strategy](doc_strategy.qmd)
- [Fourier Transforms](dft_notes.qmd)
- [Velocity to Strain Rate](velocity_to_strain_rate.qmd)
35 changes: 35 additions & 0 deletions docs/notes/patch_attrs.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: PatchAttrs
---

[`PatchAttrs`](`dascore.core.attrs.PatchAttrs`) stores metadata about a [`Patch`](`dascore.Patch`). Some attributes describe identity or provenance, some summarize coordinates, and some describe the data array itself.

This note explains how DASCore interprets a few important attributes, and its internal policies around these attributes.

## `data_type`

`data_type` is an optional label for the kind of data contained in a patch. It is useful for display defaults, plotting choices, grouping, and quick inspection, but it is not the canonical source of physical meaning, rather the data and coordinate units, as well as the patch history serve this purpose.

:::{.callout-note}
A stale or misleading `data_type` is much worse than an empty one.
:::

| Situation | `data_type` behavior |
|---|---|
| Output is still the same measured quantity, just filtered/resampled/selected/reordered | Preserve existing `data_type`. |
| Output is a known derived product with a stable meaning | Set a specific snake_case `data_type`. |
| Output changes physical meaning but no stable label is appropriate | Clear `data_type` to `""`. |

DASCore-assigned `data_type` values should be snake_case and listed in `VALID_DATA_TYPES` in [`dascore.constants`](`dascore.constants`). Correctness-critical code should prefer units, coordinates, and explicit validation.

### Patch functions

[`patch_function`](`dascore.utils.patch.patch_function`) can manage output `data_type` for patch methods.

| Decorator value | Behavior |
|---|---|
| `data_type=None` | Preserve the returned patch's `data_type`. This is the default for backward compatibility. |
| `data_type=""` | Clear the returned patch's `data_type`. |
| `data_type="some_value"` | Set the returned patch's `data_type` to that value. |

Functions may still require a specific input label with `required_attrs`, for example `required_attrs={"data_type": "velocity"}`. This should only be used when the function's assumptions truly depend on that label and are documented.
Loading
Loading