diff --git a/.agents/agents.md b/.agents/agents.md
index 854ddb09d..840170715 100644
--- a/.agents/agents.md
+++ b/.agents/agents.md
@@ -42,16 +42,18 @@ pytest dascore --doctest-modules
## Docs
-- `.qmd` under `docs/`; API docs come from docstrings. Update with any behavior or API change. Do not hard-wrap prose.
-- Edit `scripts/_templates/_quarto.yml` for site structure; `docs/_quarto.yml` is generated.
+- `.qmd` in the top-level section directories (`tutorial/`, `recipes/`, `notes/`, `contributing/`, `about/`), with `index.qmd` as the landing page. API docs come from docstrings. Update with any behavior or API change. Do not hard-wrap prose.
+- Site structure, styling and the curated API reference are configured in `great-docs.yml`; the `great-docs/` directory is generated. New public API needs a reference entry, and `tests/test_doc_coverage.py` fails when it has none.
```bash
-python scripts/build_api_docs.py && quarto render docs
+pip install "great-docs>=0.16"
+great-docs build # writes great-docs/_site
+great-docs preview # serves the built site
```
### Changelog
-No changelog file, and do not add one — no `CHANGELOG.md`, `changelog.d/`, or "unreleased" sections. `docs/changelog.qmd` is a stub pinned by `tests/test_changelog.py`. Put the summary in the PR's required `## Changelog` section, formatted per "Changelog entries" in `docs/contributing/general_guidelines.qmd`; `.github/scripts/check_pr_changelog.py` is the parser CI runs.
+No changelog file, and do not add one — no `CHANGELOG.md`, `changelog.d/`, or "unreleased" sections; the site's changelog page is generated from the GitHub releases at build time, and `tests/test_changelog.py` fails if a source page appears. Put the summary in the PR's required `## Changelog` section, formatted per "Changelog entries" in `contributing/general_guidelines.qmd`; `.github/scripts/check_pr_changelog.py` is the parser CI runs.
## Before handing off
diff --git a/.github/actions/build-docs/action.yml b/.github/actions/build-docs/action.yml
index ae83a258c..e8b76825b 100644
--- a/.github/actions/build-docs/action.yml
+++ b/.github/actions/build-docs/action.yml
@@ -1,13 +1,31 @@
name: "Build DASCore Docs"
-description: "Builds DASCore's Documentation."
+description: "Builds DASCore's Documentation with great-docs."
runs:
using: "composite"
steps:
- - uses: ./.github/actions/prep_doc_build
+ - name: Install quarto
+ uses: quarto-dev/quarto-actions/setup@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0
+ with:
+ # 1.3.x dies with "RangeError: Invalid string length" once the
+ # generated API sidebar gets large enough; see
+ # quarto-dev/quarto-cli#10504.
+ version: 1.8.27
- - name: build quarto project
- shell: bash
+ - name: print quarto version
+ shell: bash -l {0}
run: |
- quarto render docs
+ quarto --version
+
+ - name: install great-docs
+ shell: bash -l {0}
+ run: |
+ # >=0.16 renders class signatures with their parameters and points
+ # See Also links at the pages the reference actually generates.
+ python -m pip install "great-docs>=0.16"
+
+ - name: build documentation
+ shell: bash -l {0}
+ run: |
+ great-docs build
diff --git a/.github/actions/prep_doc_build/action.yml b/.github/actions/prep_doc_build/action.yml
deleted file mode 100644
index cd350c166..000000000
--- a/.github/actions/prep_doc_build/action.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-name: "prepare for doc build"
-description: "Installs quarto, renders api docs, prints quarto version"
-
-runs:
- using: "composite"
- steps:
- - name: Install quarto
- uses: quarto-dev/quarto-actions/setup@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0
- with:
- # 1.3.x dies with "RangeError: Invalid string length" once the
- # generated _quarto.yml API sidebar gets large enough; see
- # quarto-dev/quarto-cli#10504.
- version: 1.8.27
-
- - name: print quarto version
- shell: bash
- run: |
- quarto --version
-
- - name: render API docs
- shell: bash
- run: |
- python scripts/build_api_docs.py
diff --git a/.github/workflows/build_deploy_master_docs.yaml b/.github/workflows/build_deploy_master_docs.yaml
index 034b73633..5698a39ee 100644
--- a/.github/workflows/build_deploy_master_docs.yaml
+++ b/.github/workflows/build_deploy_master_docs.yaml
@@ -49,12 +49,17 @@ jobs:
python-version: ${{ steps.shared-vars.outputs.python-default }}
prepare-test-data: "true"
- - uses: ./.github/actions/prep_doc_build
+ - uses: ./.github/actions/build-docs
- name: publish docs to netlify
shell: bash
env:
- QUARTO_PRINT_STACK: true
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
run: |
- quarto publish docs --no-prompt --no-browser
+ # The site id previously lived in docs/_publish.yml, which
+ # `quarto publish` read; great-docs emits a plain directory, so
+ # deploy it with netlify's own CLI instead.
+ npx --yes netlify-cli@23 deploy \
+ --prod --no-build \
+ --dir great-docs/_site \
+ --site da79b12f-cb25-4fcc-aeb6-f19705848130
diff --git a/.github/workflows/build_deploy_stable_docs.yaml b/.github/workflows/build_deploy_stable_docs.yaml
index abe6f3ca5..e89685111 100644
--- a/.github/workflows/build_deploy_stable_docs.yaml
+++ b/.github/workflows/build_deploy_stable_docs.yaml
@@ -81,11 +81,11 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
- path: 'docs/_site'
+ path: 'great-docs/_site'
- name: Zip doc build
if: startsWith(github.ref, 'refs/tags/')
- run: zip docs.zip docs/_site -r
+ run: zip docs.zip great-docs/_site -r
- name: Upload release docs
if: startsWith(github.ref, 'refs/tags/')
diff --git a/.github/workflows/test_doc_build.yml b/.github/workflows/test_doc_build.yml
index a79086184..869e2b1b1 100644
--- a/.github/workflows/test_doc_build.yml
+++ b/.github/workflows/test_doc_build.yml
@@ -49,7 +49,9 @@ jobs:
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: documentation_build_test
- path: ./docs/_site
+ path: ./great-docs/_site
+ # .well-known/agent-skills must survive packaging
+ include-hidden-files: true
retention-days: 1
- name: Generate documentation URL
diff --git a/.gitignore b/.gitignore
index 9bbadd6ab..54bd86513 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,9 +52,6 @@ coverage.xml
# logs:
*.log
-# Sphinx documentation
-docs/_build/
-
# PyBuilder
target/
@@ -70,12 +67,9 @@ target/
.DS_store*
# docs
-docs/api/*
_autosummary
.quarto/
-docs/site_libs
-docs/_quarto.yml
-docs/**/*.ipynb
+**/*.quarto_ipynb
.cross_ref.json
# profile stuff from asv
@@ -88,8 +82,7 @@ scratch/**
uv.lock
tests/test_autogenerated_doccode/
-docs/index_files
-docs/index.quarto_ipynb
+index_files/
# Agent stuff
.codex
@@ -114,3 +107,6 @@ prof/
.vscode/
#JetBrains
.idea/
+great-docs/
+# Quarto freeze cache written by great-docs builds.
+_freeze/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 10ef6d235..eb3914ed2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,4 +1,3 @@
-exclude: scripts/_templates
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
diff --git a/docs/acknowledgements.qmd b/about/acknowledgements.qmd
similarity index 74%
rename from docs/acknowledgements.qmd
rename to about/acknowledgements.qmd
index 102c5a840..d8dee05ea 100644
--- a/docs/acknowledgements.qmd
+++ b/about/acknowledgements.qmd
@@ -1,10 +1,10 @@
---
+title: Acknowledgments
+description: "Funding sources and acknowledgments."
---
-# Acknowledgments
-
- DASDAE is supported in part by the NSF Geoinformatics Program, under grant [#2148614](https://nsf.gov/awardsearch/showAward?AWD_ID=2148614)
diff --git a/docs/contributors.qmd b/about/contributors.qmd
similarity index 52%
rename from docs/contributors.qmd
rename to about/contributors.qmd
index 9f117d85c..1e8e29e8b 100644
--- a/docs/contributors.qmd
+++ b/about/contributors.qmd
@@ -1,3 +1,6 @@
-# Contributors
+---
+title: Contributors
+description: "Thanks to everyone who has contributed to DASCore."
+---
A huge thanks to [all the DASCore contributors](https://github.com/DASDAE/dascore/graphs/contributors)!
diff --git a/docs/supported_formats.qmd b/about/supported_formats.qmd
similarity index 86%
rename from docs/supported_formats.qmd
rename to about/supported_formats.qmd
index 22e8fa462..8b80f1857 100644
--- a/docs/supported_formats.qmd
+++ b/about/supported_formats.qmd
@@ -1,5 +1,6 @@
---
title: Supported File Formats
+description: "File formats DASCore can read and write."
execute:
warning: false
---
diff --git a/docs/supported_plugins.qmd b/about/supported_plugins.qmd
similarity index 86%
rename from docs/supported_plugins.qmd
rename to about/supported_plugins.qmd
index c4b68e204..3c05dd4cb 100644
--- a/docs/supported_plugins.qmd
+++ b/about/supported_plugins.qmd
@@ -1,5 +1,6 @@
---
title: Supported Third-Party Plugins
+description: "Packages that add namespaces to DASCore's Patch and Spool."
execute:
warning: false
---
diff --git a/assets/dascore.scss b/assets/dascore.scss
new file mode 100644
index 000000000..5cc400247
--- /dev/null
+++ b/assets/dascore.scss
@@ -0,0 +1,269 @@
+/*
+ * DASCore theme layer.
+ *
+ * Layered after `yeti` (the base Bootswatch theme the previous doc build used)
+ * and `great-docs.scss`, so this file only has to restore the handful of places
+ * where great-docs' own styling diverges from the old site, plus the custom
+ * rules that used to live in `docs/styles.css`.
+ *
+ * Quarto applies `scss:defaults` blocks in reverse theme order, so variables
+ * set here win over `great-docs.scss` and `yeti` -- but only where those files
+ * declare them with `!default`. great-docs assigns some outright (notably the
+ * heading sizes), and those can only be overridden from `scss:rules`.
+ */
+
+/*-- scss:defaults --*/
+
+// Code blocks. Quarto derives their background from `$progress-bg`, which has
+// drifted lighter since the old site was built; pin it to the grey that site
+// rendered so code cells keep the same weight on the page.
+$code-block-bg: rgba(204, 204, 204, 0.65);
+
+/*-- scss:rules --*/
+
+// Body copy: the old site set this literally in docs/styles.css, on top of an
+// unchanged 16px root. Keeping it a plain rule (rather than raising
+// `$font-size-base`) reproduces that exactly: prose grows, while Bootstrap's
+// rem-based sizing and great-docs' components are left alone.
+body {
+ font-size: 18px;
+}
+
+// great-docs shrinks h2/h3 to 1.5rem/1.3rem and drops the h2 underline Quarto
+// uses to separate major sections; the old site had Quarto's defaults for both.
+//
+// These are rules rather than `$h2-font-size` overrides because great-docs
+// assigns those variables without `!default`, so a defaults block cannot win
+// regardless of theme order.
+// The sizes are the responsive pair Bootstrap's RFS emitted for the old site,
+// not flat rem values: h1 stays fluid (`calc(1.325rem + 0.9vw)`), so a fixed h2
+// would overtake it on narrow screens and invert the heading hierarchy.
+h2,
+.h2 {
+ font-size: calc(1.29rem + 0.48vw);
+ // `--bs-border-color` is #dee2e6 in light mode, which is exactly what the
+ // old site drew, and Bootstrap darkens it under `data-bs-theme="dark"`.
+ border-bottom: 1px solid var(--bs-border-color);
+ padding-bottom: 0.5rem;
+}
+
+h3,
+.h3 {
+ font-size: calc(1.27rem + 0.24vw);
+}
+
+@include media-breakpoint-up(xl) {
+ h2,
+ .h2 {
+ font-size: 1.65rem;
+ }
+
+ h3,
+ .h3 {
+ font-size: 1.45rem;
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Content tables (ported from docs/styles.css)
+//
+// Scoped to prose tables so great-docs' own components -- API summary tables
+// and Great Tables output -- keep their styling.
+// ---------------------------------------------------------------------------
+
+main.content {
+ // docs/styles.css only ever ran in light mode; these tokens give its colors
+ // a dark-mode counterpart so the ported rules below work in both themes.
+ --dascore-table-border: #{$gray-400};
+ --dascore-table-stripe: #{rgba(210, 209, 209, 0.35)};
+
+ table:not(.gt_table):not(.gd-summary-table) {
+ border-collapse: collapse;
+ border: 2px solid var(--dascore-table-border);
+ letter-spacing: 1px;
+ font-size: 0.8rem;
+ width: 100%;
+
+ caption {
+ padding: 10px;
+ caption-side: top;
+ color: $gray-600;
+ text-align: center;
+ letter-spacing: 1px;
+ font-size: small;
+ }
+
+ th,
+ td {
+ border: 0.5px solid var(--dascore-table-border);
+ padding: 10px 20px;
+ }
+
+ th {
+ text-align: center;
+ }
+
+ td {
+ text-align: left;
+ vertical-align: middle;
+ }
+
+ // Keep the first column (usually a name or format) on one line.
+ td:first-child {
+ white-space: nowrap;
+ }
+
+ tbody tr:nth-child(odd) {
+ background-color: var(--dascore-table-stripe);
+ }
+ }
+}
+
+body.quarto-dark main.content {
+ --dascore-table-border: #{$gray-700};
+ --dascore-table-stripe: #{rgba(255, 255, 255, 0.05)};
+}
+
+// ---------------------------------------------------------------------------
+// Fixes for great-docs rendering under `shift-heading-level-by: -1`
+// ---------------------------------------------------------------------------
+
+// great-docs styles docstring "Notes" sections as an info box for h2/h3, but
+// the heading shift promotes them to h1 on API pages, which loses the box.
+$doc-notes-accent: #3b82f6;
+
+section:has(> h1.doc-notes) {
+ border-left: 4px solid $doc-notes-accent;
+ background-color: rgba($doc-notes-accent, 0.08);
+ padding: 0.75rem 1rem;
+ margin: 1rem 0;
+
+ > h1.doc-notes {
+ font-size: 0.9rem;
+ font-weight: 600;
+ margin: 0 0 0.25rem 0;
+ border-bottom: none;
+ padding-bottom: 0;
+
+ &::before {
+ content: "\2139\FE0F\00a0";
+ }
+ }
+
+ > p:last-child {
+ margin-bottom: 0;
+ }
+}
+
+// Long entries in the left nav should wrap at word boundaries, not mid-word.
+#quarto-sidebar {
+ .sidebar-item-text,
+ .sidebar-item .menu-text {
+ overflow-wrap: break-word;
+ word-break: normal;
+ hyphens: none;
+ }
+}
+
+// Navbar. great-docs brands it with a hard-coded font, bold weight and a boxed
+// outline around the title; the old site showed plain navbar text in the theme
+// font, so undo the branding and let the title use its full width.
+.navbar {
+ .navbar-title {
+ max-width: none;
+ font-family: inherit;
+ font-weight: normal;
+ border: none;
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .navbar-brand-container {
+ flex-shrink: 0;
+ }
+}
+
+// Matching great-docs' own selector so the weight override actually applies.
+#navbarCollapse > ul.navbar-nav.navbar-nav-scroll.me-auto > li > a {
+ font-weight: normal;
+}
+
+// The active sidebar entry is orange in great-docs, which clashes with the
+// palette the rest of the site inherits from yeti. Keep the underline
+// affordance, but draw it in the theme color. Light mode hard-codes the orange
+// on this selector; dark mode reads the `--gd-active-link` token, so recolor
+// the token there and anything else bound to it follows.
+#quarto-sidebar a.sidebar-item-text.sidebar-link.active {
+ color: $primary;
+ text-decoration-color: rgba($primary, 0.5);
+}
+
+body.quarto-dark {
+ --gd-active-link: #{tint-color($primary, 45%)};
+}
+
+// The landing page hero already shows the project name, so drop the duplicate
+// document title Quarto emits below it.
+body:has(.gd-hero) {
+ .gd-hero {
+ padding-top: 1.5rem;
+ }
+
+ main > header#title-block-header h1.title {
+ display: none;
+ }
+}
+
+// Section index cards (the generated Recipes/Notes/Contributing/About
+// landing pages). great-docs lays out description-only entries as a
+// single-column grid of tall, generously padded boxes, which reads as a stack
+// of banners; this makes them a responsive multi-column grid of smaller cards.
+//
+// The generated markup carries these rules as inline styles, so the overrides
+// have to be `!important` to win. Everything great-docs does not set inline
+// (hover, dark mode) is left as a normal rule.
+.section-cards.section-cards-list {
+ grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)) !important;
+ gap: 0.75rem !important;
+
+ .section-card {
+ padding: 0.75rem 0.9rem !important;
+ border-color: $border-color !important;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+
+ &:hover {
+ border-color: rgba($primary, 0.6) !important;
+ box-shadow: 0 0.125rem 0.35rem rgba(0, 0, 0, 0.08);
+ }
+ }
+
+ .section-card-title {
+ font-size: 1rem !important;
+ margin-bottom: 0.2rem !important;
+ color: $primary !important;
+ }
+
+ .section-card-desc {
+ font-size: 0.85rem !important;
+ line-height: 1.4 !important;
+ }
+}
+
+body.quarto-dark .section-cards.section-cards-list {
+ .section-card {
+ border-color: rgba(255, 255, 255, 0.15) !important;
+
+ &:hover {
+ border-color: rgba(tint-color($primary, 45%), 0.6) !important;
+ box-shadow: none;
+ }
+ }
+
+ .section-card-title {
+ color: tint-color($primary, 45%) !important;
+ }
+
+ .section-card-desc {
+ color: rgba(255, 255, 255, 0.65) !important;
+ }
+}
diff --git a/docs/_static/diataxis.png b/assets/diataxis.png
similarity index 100%
rename from docs/_static/diataxis.png
rename to assets/diataxis.png
diff --git a/docs/_static/logo.png b/assets/logo.png
similarity index 100%
rename from docs/_static/logo.png
rename to assets/logo.png
diff --git a/docs/_static/logo.svg b/assets/logo.svg
similarity index 100%
rename from docs/_static/logo.svg
rename to assets/logo.svg
diff --git a/docs/_static/patch_n_spool.png b/assets/patch_n_spool.png
similarity index 100%
rename from docs/_static/patch_n_spool.png
rename to assets/patch_n_spool.png
diff --git a/docs/contributing/adding_test_data.qmd b/contributing/adding_test_data.qmd
similarity index 96%
rename from docs/contributing/adding_test_data.qmd
rename to contributing/adding_test_data.qmd
index deb19438b..bf87ff1e0 100644
--- a/docs/contributing/adding_test_data.qmd
+++ b/contributing/adding_test_data.qmd
@@ -1,10 +1,11 @@
---
title: Adding Test Data
+description: "How to add small test files and generated patches to DASCore."
---
There are a few different way to add test data to dascore. The key, however, is to ensure test files and generated patches are small (a few mb at most) so the documentation and test suite still run quickly.
-# Adding functions which create example data
+## Adding functions which create example data
The [examples module](`dascore.examples`) contains several functions for creating example `Patch` and `Spool` instances. You can add a new function in that module which creates a new patch or spool, then just register the function so it can be called from `dc.get_example_patch` or `dc.get_example_spool`. These should be simple objects which can be generated within python. If you need to download a file see
[adding a data file](#adding_a_data_file).
@@ -41,7 +42,7 @@ spool_example = dc.get_example_spool("new_das_spool")
If, in the test code, the example patch or spool is used only once, just call the get_example function in the test. If it is needed multiple times, consider putting it in a fixture. See [testing](./testing.qmd) for more on fixtures.
-# Adding a data file
+## Adding a data file
Of course, not all data can easily be generated in python. For example, testing [support for new file formats](./new_format.qmd) typically requires a test file.
diff --git a/docs/contributing/code_of_conduct.qmd b/contributing/code_of_conduct.qmd
similarity index 97%
rename from docs/contributing/code_of_conduct.qmd
rename to contributing/code_of_conduct.qmd
index da65e7a57..ccb5c6445 100644
--- a/docs/contributing/code_of_conduct.qmd
+++ b/contributing/code_of_conduct.qmd
@@ -1,5 +1,7 @@
-
-# Contributor Covenant Code of Conduct
+---
+title: Contributor Covenant Code of Conduct
+description: "The Contributor Covenant standards for our community."
+---
## Our Pledge
diff --git a/docs/contributing/contributing.qmd b/contributing/contributing.qmd
similarity index 95%
rename from docs/contributing/contributing.qmd
rename to contributing/contributing.qmd
index 3d074698e..40163452e 100644
--- a/docs/contributing/contributing.qmd
+++ b/contributing/contributing.qmd
@@ -1,11 +1,12 @@
---
-title: Contributing
+title: Contributing Overview
+description: "How to get involved and contribute to DASCore."
---
Contributions to DASCore are welcomed and appreciated. Before contributing
please be aware of our [code of conduct](code_of_conduct.qmd).
-# Prerequisites
+## Prerequisites
To start, clone DASCore and [install it in development mode](dev_install.qmd). If you are new to contributing to open-source projects, this [recipe](../recipes/how_to_contribute.qmd) provides step-by-step instructions.
@@ -14,17 +15,17 @@ Review how [DASCore's testing](testing.qmd) works, [how DASCore is documented](d
-# Planning
+## Planning
Development planning and prioritization takes place [here](https://github.com/orgs/DASDAE/projects/2).
-# DASCore or other DASDAE packages
+## DASCore or other DASDAE packages
You may wonder whether a new feature you'd like to add belongs in DASCore, or if it should be part of another [DASDAE](https://github.com/DASDAE) package. The guiding principle is that if it does not require additional dependencies and is not particularly specialized to one sub-area of applied seismology, then it can be part of DASCore. What if the feature you're interested in adding is generally applicable for many kinds of DAS data analysis, but requires some additional package dependency? If this is the case, open a discussion describing the feature, the additional dependency, and (optional but encouraged) other future features that may also share this dependency. Typically we'll try to come to a clear consensus, then you can move ahead with development. If the proposed DASCore dependency addition appears to be controversial, then you will deliver a short presentation at one of the bi-weekly DASDAE developer team check-ins that should describe the feature, the additional dependency (including the approximate size of the additional software to be installed), and ideas for other features this dependency could enable. Then the developer community in attendance will discuss the proposed change and take a vote (majority approval required).
If you are interested in creating a new [DASDAE](https://github.com/DASDAE) package which uses DASCore as a dependency, you are not required to hold it to the same style and testing guidelines as DASCore, but you are encouraged to do so, and can use DASCore's setup as an example. Following a common set of style and contributor workflows will make it easier for us to develop a community of DASDAE developers who can easily move between using and developing any of the DASDAE packages.
-# Leadership
+## Leadership
Currently DASCore operates in BDFL mode during its initial construction phase, with Derrick Chambers leading the oversight of the master branch, but this is not intended to be the mode of operation in the future. At a DASDAE developer team check-in (currently biweekly), the team reviews contributions and nominates a leadership team for the next cycle based on contribution history.
diff --git a/docs/contributing/dev_install.qmd b/contributing/dev_install.qmd
similarity index 89%
rename from docs/contributing/dev_install.qmd
rename to contributing/dev_install.qmd
index b4d86ee90..c74e0d042 100644
--- a/docs/contributing/dev_install.qmd
+++ b/contributing/dev_install.qmd
@@ -1,17 +1,18 @@
---
title: DASCore Development Installation
+description: "Set up DASCore for local development."
---
The following steps are needed to set up `DASCore` for development:
-## 1. Clone DASCore
+### 1. Clone DASCore
```bash
git clone https://github.com/dasdae/dascore
cd dascore
```
-## 2. Pull tags
+### 2. Pull tags
Make sure to pull all of the latest git tags.
@@ -20,7 +21,7 @@ Make sure to pull all of the latest git tags.
git pull origin master --tags
```
-## 3. Create a virtual environment (optional)
+### 3. Create a virtual environment (optional)
Create and activate a virtual environment so DASCore will not mess with the base (or system) python installation.
@@ -38,7 +39,7 @@ conda env create -f environment.yml
conda activate dascore
```
-## 4. Install DASCore in development mode
+### 4. Install DASCore in development mode
```bash
pip install -e ".[dev]"
@@ -50,7 +51,7 @@ If you created the environment with `uv venv`, use `uv pip` instead. A uv virtua
uv pip install -e ".[dev]"
```
-## 5. Setup pre-commit hooks
+### 5. Setup pre-commit hooks
dascore uses several [pre-commit](https://pre-commit.com/) hooks to ensure the code stays tidy. Please install and use them!
@@ -58,7 +59,7 @@ dascore uses several [pre-commit](https://pre-commit.com/) hooks to ensure the c
pre-commit install -f
```
-# Refresh
+## Refresh
If you have already installed dascore but it has been a while, please do the following before creating a new branch:
diff --git a/docs/contributing/documentation.qmd b/contributing/documentation.qmd
similarity index 78%
rename from docs/contributing/documentation.qmd
rename to contributing/documentation.qmd
index 6e83c55ea..37fc3d1b7 100644
--- a/docs/contributing/documentation.qmd
+++ b/contributing/documentation.qmd
@@ -1,15 +1,16 @@
---
title: Documentation
+description: "The levels of DASCore documentation and how they fit together."
---
There are several levels of documentation in DASCore. These include: code comments (which are primarily for developers), docstrings (which are used to generate the API docs), and markdown documentation pages like this one which have a .qmd extension.
-# Code comments
+## Code comments
Code comments are primarily for developers. They should describe useful information and not just restate obvious parts of the code. Consider refactoring with better named variables or smaller, well named functions if you find yourself making lots of these types of comments.
Don't be afraid to make multi-line comments if needed.
-# Doc-Strings
+## Doc-Strings
Use [numpy style docstrings](https://numpydoc.readthedocs.io/en/latest/format.html). All public code (doesn't start with a `_`) should have a "full" docstring but private code (starts with a `_`) can have an abbreviated docstring.
@@ -69,7 +70,7 @@ def _recombobulate(df, arg1, arg2):
```
-## Examples in docstrings
+### Examples in docstrings
Examples in docstrings can be done using the standard [doctest](https://docs.python.org/3/library/doctest.html) syntax as above, or [quarto code blocks](https://quarto.org/docs/output-formats/html-code.html) can be used directly. The latter gives more control over outputs and display options.
@@ -95,7 +96,7 @@ would produce the following code in the examples section:
````raw
```{{python}}
#| code-fold: true
-# This is a base example
+## This is a base example
print(1 + 2)
```
### This is a sub-section
@@ -105,52 +106,56 @@ print("cool beans")
```
````
-# Generating Documentation
+## Generating Documentation
-DASCore's documentation is built with [quarto](https://quarto.org/). In order to build the documentation, you must first [install DASCore in development mode](dev_install.qmd) then [install quarto](https://quarto.org/docs/get-started/).
-
-Next, the automatic API documents are created with scripts/build_api_docs.py
+DASCore's documentation is built with [great-docs](https://posit-dev.github.io/great-docs/), which is based on [quarto](https://quarto.org/). In order to build the documentation, you must first [install DASCore in development mode](dev_install.qmd), [install quarto](https://quarto.org/docs/get-started/), then install great-docs:
```bash
-python scripts/build_api_docs.py
+pip install great-docs
```
-Finally, the documentation can be built by calling quarto render on the docs folder:
+The entire site (API reference included) is then built with a single command run from the repository root:
```bash
-quarto render docs
+great-docs build
```
-The newly generated html can then be accessed by double-clicking on the html index at docs/_site/index.html.
+The newly generated html can then be accessed by double-clicking on the html index at great-docs/_site/index.html.
Conversely, you can also preview the documentation so changes are rendered in real time in the browser:
```bash
-quarto preview docs
+great-docs preview
```
:::{.callout-warning}
-If you need to change the structure of the site, like adding a new section/subsection, the file to edit is scripts/_templates/_quarto.yml.
-Do not modify docs/_quarto.yml because it will be overwritten.
+The site structure, the curated API reference, and everything else about the build are configured in great-docs.yml at the repository root.
+The great-docs/ directory is an ephemeral build directory; do not edit or commit it.
:::
-# Cross references
+## Cross references
Cross references provide a means of linking parts of the documentation to the API docs for specific modules, classes, functions, or methods. They work just like normal markdown links except the reference is a dascore object surrounded by backticks like so:
```
-This is a link to DASCore's [Patch](`dascore.core.Patch`).
+This is a link to DASCore's [Patch](`dascore.Patch`).
```
-To link to qmd files in the documentation folder (eg from a docstring) the path relative to the docs folder can be used:
+Any public access path to a documented object works, including method aliases
+and re-export paths (e.g. `` `dascore.Patch.pass_filter` `` or
+`` `dascore.proc.pass_filter` ``); scripts/greatdocs_alias_inventory.py adds
+these aliases to the link inventory during the build. Module references such
+as `` `dascore.proc` `` link to the matching section of the API index.
+
+To link to a documentation page (eg from a docstring), use its site-root path:
```
-This is a link to [this qmd file](`docs/contributing/documentation.qmd`).
+See the [patch tutorial](/tutorial/patch.qmd) for examples.
```
Cross references can be used in both docstrings and the qmd documentation pages.
-# Referencing publications
+## Referencing publications
New references should be added to the references.bib file in the docs folder.
@@ -165,7 +170,7 @@ Citing @lindsey2021fiber or [@lindsey2021fiber]
References then show up at the bottom of the page, or if the mouse pointer hovers over the link.
-# Equations
+## Equations
Equations use standard LaTeX. Inline equations ($E=mc^2$) are surrounded by a single dollar sign (`$`) like this: `$E=mc^2$`. Multiline equations start and end with double dollar signs (`$$`) and can be given a referenceable label. For example:
@@ -191,6 +196,6 @@ $${#eq-rotations}
and @eq-rotations is referenced by `@eq-rotations`.
-# Additional Tips
+## Additional Tips
For more information about figure alignment, code blocks, formatting, etc. checkout the excellent [quarto docs](https://quarto.org/).
diff --git a/docs/contributing/extending_dascore.qmd b/contributing/extending_dascore.qmd
similarity index 98%
rename from docs/contributing/extending_dascore.qmd
rename to contributing/extending_dascore.qmd
index 1d7d82a6b..d71708964 100644
--- a/docs/contributing/extending_dascore.qmd
+++ b/contributing/extending_dascore.qmd
@@ -1,5 +1,6 @@
---
title: Extending DASCore
+description: "Add custom Patch and Spool namespaces via plugins."
---
This page explains how to extend DASCore with `Patch` and `Spool` namespaces. Use it when you want users to write code like `patch.my_plugin.some_method()` or `spool.my_plugin.some_method()` from a separate package.
diff --git a/docs/contributing/general_guidelines.qmd b/contributing/general_guidelines.qmd
similarity index 93%
rename from docs/contributing/general_guidelines.qmd
rename to contributing/general_guidelines.qmd
index 741b3b041..5990538f6 100644
--- a/docs/contributing/general_guidelines.qmd
+++ b/contributing/general_guidelines.qmd
@@ -1,9 +1,10 @@
---
title: Guidelines
+description: "A few guidelines to follow when developing DASCore."
---
This page highlights a few guidelines for DASCore development.
-# Branching and versioning
+## Branching and versioning
We create new features or bug fixes in their own branches and merge them into `dev` via pull requests. `dev` is the
integration branch; it is merged into `master` at release time. We may switch to a more complex branching model if
@@ -15,7 +16,7 @@ follow [semantic versioning](https://semver.org/), meaning we will not bump the
is more stable.
-# Changelog entries
+## Changelog entries
DASCore keeps no changelog file; the [release notes](https://github.com/DASDAE/dascore/releases) are assembled from
merged pull requests. Every pull request therefore needs a `## Changelog` section, which CI checks. Write one bullet
@@ -32,12 +33,12 @@ Write the text after the colon as a complete sentence, with a subject and a verb
`none` may be written bare or as the section's only bullet (`- none`); both pass.
-# Paths
+## Paths
Prefer `pathlib.Path` to strings when working with paths. However, when dealing with many many files (e.g., indexers)
strings may be preferred for efficiency.
-# Working with dataframes
+## Working with dataframes
Column names should be snake_cased whenever possible.
diff --git a/docs/contributing/new_format.qmd b/contributing/new_format.qmd
similarity index 95%
rename from docs/contributing/new_format.qmd
rename to contributing/new_format.qmd
index c95a7a490..138fcd6c3 100644
--- a/docs/contributing/new_format.qmd
+++ b/contributing/new_format.qmd
@@ -1,5 +1,6 @@
---
title: Adding a New Format
+description: "Add IO support for a new file format."
---
This page details how to add IO support for a new format to DASCore. The steps are:
@@ -142,7 +143,7 @@ Every `FiberIO.scan` implementation must accept `snap: bool = True`, either expl
- `snap=True` (the default) preserves existing behavior. Formats may represent stored sample times as an idealized uniform range.
- `snap=False` means returned coords must represent stored coordinate values exactly. For header-defined formats whose coordinates are already specified by start, step, and sample count, this is a documented no-op.
-Formats that read stored per-sample coordinate arrays must not use bare `get_coord(array)` for the exact path because its tolerant uniformity inference can snap small jitter. Use the shared [`get_exact_coord`](`dascore.io.utils.get_exact_coord`) helper, which builds a value-preserving coordinate via `CoordSegmented.from_array(values, tolerance=0, units=...)`: a truly uniform array becomes a `CoordRange`, a piecewise-uniform array exposes its internal sampling changes through `CoordSegmented`, and a non-monotonic array falls back to a plain array coordinate. The helper also guards against pathological input: an array whose sub-step jitter would produce roughly one segment per sample is returned as a plain monotonic coordinate rather than an over-segmented one, keeping every value exact without the memory and construction cost of per-sample seams.
+Formats that read stored per-sample coordinate arrays must not use bare `get_coord(array)` for the exact path because its tolerant uniformity inference can snap small jitter. Use the shared `get_exact_coord` helper (in `dascore.io.utils`), which builds a value-preserving coordinate via `CoordSegmented.from_array(values, tolerance=0, units=...)`: a truly uniform array becomes a `CoordRange`, a piecewise-uniform array exposes its internal sampling changes through `CoordSegmented`, and a non-monotonic array falls back to a plain array coordinate. The helper also guards against pathological input: an array whose sub-step jitter would produce roughly one segment per sample is returned as a plain monotonic coordinate rather than an over-segmented one, keeping every value exact without the memory and construction cost of per-sample seams.
`snap=False` describes the coordinate values *stored* by the format, which are not necessarily what `read(...)` returns. A format whose `read` reconstructs coordinates from header start/step/count metadata (e.g. ProdML) will still return that idealized range from `read`, while `scan_payloads(..., snap=False)` reports the exact stored per-sample array. The common IO conformance test only compares scan and read coordinates for formats whose `read` also accepts `snap`/`snap_dims`.
@@ -392,7 +393,7 @@ The name and version of the format are separated by a double underscore.
## Directories as Inputs
-Some `FiberIO` formats may not be self-contained files, but rather must be understood in the context of an entire directory. In these cases, the `input_type` parameter on the `FiberIO` subclass should be set to "directory". See the [xml_binary](`dascore.io.xml_binary`) module for an example of a directory based `FiberIO` implementation.
+Some `FiberIO` formats may not be self-contained files, but rather must be understood in the context of an entire directory. In these cases, the `input_type` parameter on the `FiberIO` subclass should be set to "directory". See the [xml_binary](https://github.com/DASDAE/dascore/tree/master/dascore/io/xml_binary) module for an example of a directory based `FiberIO` implementation.
:::{.callout-warning}
DASCore assumes a directory-based `FiberIO` does not have any sub patch files of a different format. Once a valid `FiberIO` directory is found, contents of the directory are no longer searched for Patch files.
diff --git a/docs/contributing/profiling_benchmarks.qmd b/contributing/profiling_benchmarks.qmd
similarity index 94%
rename from docs/contributing/profiling_benchmarks.qmd
rename to contributing/profiling_benchmarks.qmd
index bd4b23b88..fe3724f87 100644
--- a/docs/contributing/profiling_benchmarks.qmd
+++ b/contributing/profiling_benchmarks.qmd
@@ -1,7 +1,8 @@
---
title: Profiling and Benchmarks
+description: "Run and write DASCore's CodSpeed benchmark suite."
---
-# Benchmarks
+## Benchmarks
DASCore uses [codspeed](https://codspeed.io/) to create and run a simple benchmark suite. The benchmarks are found in the benchmarks folder at the top level of the repository.
@@ -14,7 +15,7 @@ However, when you create a pull request, the benchmarks will be run in the CI/CD
If you add significant new functionality, you should probably add a benchmark.
-# Profiling
+## Profiling
If you find a significant issue, you can profile the problematic benchmark(s) to see *why* their performance degraded. This can be done with the [pytest profile plugin](https://pypi.org/project/pytest-profiling/).
diff --git a/docs/contributing/publish_a_new_release.qmd b/contributing/publish_a_new_release.qmd
similarity index 99%
rename from docs/contributing/publish_a_new_release.qmd
rename to contributing/publish_a_new_release.qmd
index 3ce102298..ef55f357b 100644
--- a/docs/contributing/publish_a_new_release.qmd
+++ b/contributing/publish_a_new_release.qmd
@@ -1,5 +1,6 @@
---
title: "Publish a new release"
+description: "The maintainer workflow for publishing a DASCore release."
---
This page describes the maintainer workflow for publishing a DASCore release. DASCore's package version is derived from git tags, so the release tag is the source of truth and no version file needs to be edited by hand.
diff --git a/docs/contributing/style_and_linting.qmd b/contributing/style_and_linting.qmd
similarity index 93%
rename from docs/contributing/style_and_linting.qmd
rename to contributing/style_and_linting.qmd
index 8c34281d2..d76730983 100644
--- a/docs/contributing/style_and_linting.qmd
+++ b/contributing/style_and_linting.qmd
@@ -1,8 +1,9 @@
---
title: "Style and Linting"
+description: "Code style and linting conventions (Black, flake8)."
---
-# Linting
+## Linting
DASCore uses [Black](https://github.com/ambv/black) and [flake8](http://flake8.pycqa.org/en/latest/) for code linting.
If you have [properly installed DASCore's pre-commit hooks](dev_install.qmd#setup-pre-commit-hooks) they will be
@@ -17,7 +18,7 @@ pre-commit run --all
It is often useful to run this command twice before making changes because many of the hooks
will automatically fix the issue they raise on the first run.
-# Type Hints
+## Type Hints
DASCore makes extensive use of Python 3's [type hints](https://docs.python.org/3/library/typing.html).
Use them to annotate any public functions/methods. See the docstring section of the [documentation page](documentation.qmd)
diff --git a/docs/contributing/testing.qmd b/contributing/testing.qmd
similarity index 95%
rename from docs/contributing/testing.qmd
rename to contributing/testing.qmd
index 5fab6207c..cb036aee5 100644
--- a/docs/contributing/testing.qmd
+++ b/contributing/testing.qmd
@@ -1,8 +1,9 @@
---
title: "Testing"
+description: "Run and write DASCore's pytest test suite."
---
-# Testing
+## Testing
DASCore's test suite is run with [pytest](https://docs.pytest.org/en/stable/). While in the base dascore repo
(and after [installing DASCore for development](dev_install.qmd)) invoke pytest from the command line:
@@ -59,7 +60,7 @@ pytest tests/test_autogenerated_doccode
The `tests/test_autogenerated_doccode` directory is intentionally gitignored and should be regenerated locally rather than committed.
-## Writing Tests
+### Writing Tests
Tests should go into the `tests/` folder, which mirrors the structure of the main package.
For example, if you are writing tests for `dascore.Patch`, whose class definition is
diff --git a/dascore/core/coordmanager.py b/dascore/core/coordmanager.py
index c61ccea9f..5ba3a4642 100644
--- a/dascore/core/coordmanager.py
+++ b/dascore/core/coordmanager.py
@@ -1163,7 +1163,7 @@ def get_coord_manager(
Information about coordinates. These can be a mapping of the
form: {name, array}, {name: (dim_name, array)}, or
{name: ((dim_names,) array). Can also be a
- [`CoordManager`](`dascore.core.CoordManager`).
+ [`CoordManager`](`dascore.CoordManager`).
dims
Tuple specify dimension names
shape
diff --git a/dascore/core/coords.py b/dascore/core/coords.py
index a0cf22c17..52d09f4de 100644
--- a/dascore/core/coords.py
+++ b/dascore/core/coords.py
@@ -1,6 +1,6 @@
"""Machinery for coordinates.
-See ['Coordinate Internals'](`docs/notes/coordinate_internals.qmd`) for the
+See [Coordinate Internals](/notes/coordinate_internals.qmd) for the
current coord-family and string-coordinate design notes.
"""
@@ -2739,7 +2739,7 @@ def _raise_string_coord_error(operation: str) -> None:
class CoordString(BaseCoord):
"""A coordinate implementation for string/categorical values.
- See ['Coordinate Internals'](`docs/notes/coordinate_internals.qmd`) for the
+ See [Coordinate Internals](/notes/coordinate_internals.qmd) for the
constraints that make string coords differ from numeric and time-like
coords. Plain string selectors use exact matching unless they contain `*`
or `?`, in which case they are treated as unix-style wildcard patterns.
@@ -2943,7 +2943,7 @@ def get_coord(
Notes
-----
- See ['Coordinate Internals'](`docs/notes/coordinate_internals.qmd`) for
+ See [Coordinate Internals](/notes/coordinate_internals.qmd) for
dispatch and coord-family design notes.
The following combinations of input parameters are typical:
diff --git a/dascore/core/summary.py b/dascore/core/summary.py
index a49c62cbe..799dec55b 100644
--- a/dascore/core/summary.py
+++ b/dascore/core/summary.py
@@ -1,6 +1,6 @@
"""Summary models for patch workflows.
-See ['Coordinate Internals'](`docs/notes/coordinate_internals.qmd`) for the
+See [Coordinate Internals](/notes/coordinate_internals.qmd) for the
relationship between full coords, exact coord summaries, and flattened index metadata.
"""
diff --git a/dascore/examples.py b/dascore/examples.py
index 20bff10af..50c9e7a2a 100644
--- a/dascore/examples.py
+++ b/dascore/examples.py
@@ -308,7 +308,7 @@ def example_event_1():
@register_func(EXAMPLE_PATCHES, key="example_event_2")
def example_event_2():
"""
- [`example_event_1`](`dascore.examples.example_event_1`) with pre-processing.
+ Same as `example_event_1` but with pre-processing applied.
"""
path = fetch("example_dasdae_event_1.h5")
patch = _load_example_patch_from_file(path).update_attrs(data_type="strain_rate")
diff --git a/dascore/io/dasdae/utils.py b/dascore/io/dasdae/utils.py
index c6aea96b1..e3248b1f5 100644
--- a/dascore/io/dasdae/utils.py
+++ b/dascore/io/dasdae/utils.py
@@ -1,6 +1,6 @@
"""DASDAE format utilities.
-See ['Coordinate Internals'](`docs/notes/coordinate_internals.qmd`) for the
+See [Coordinate Internals](/notes/coordinate_internals.qmd) for the
coord serialization and string-serialization design notes used here.
"""
diff --git a/dascore/proc/aggregate.py b/dascore/proc/aggregate.py
index 0b0ad6e6a..31caf762a 100644
--- a/dascore/proc/aggregate.py
+++ b/dascore/proc/aggregate.py
@@ -47,10 +47,10 @@ def aggregate(
The aggregation to apply along dimension. Options are:
{options}
- See Also
- --------
- - See also the aggregation shortcut methods in the
- [aggregate module](`dascore.proc.aggregate`).
+ Notes
+ -----
+ The aggregation shortcut methods (`patch.mean`, `patch.std`, ...) live in
+ the [aggregate module](`dascore.proc.aggregate`) and call this function.
Examples
--------
diff --git a/dascore/proc/correlate.py b/dascore/proc/correlate.py
index f30c33c97..1fd66d83b 100644
--- a/dascore/proc/correlate.py
+++ b/dascore/proc/correlate.py
@@ -52,7 +52,7 @@ def correlate_shift(
undo_weighting
If True, also undo the weighting artifact caused by DASCore's dft
weighting. This is done by simply dividing by the coordinate step.
- See [dft note](`docs/notes/dft_notes.qmd`) for more details.
+ See [dft note](/notes/dft_notes.qmd) for more details.
Examples
--------
@@ -103,7 +103,7 @@ def correlate(
While a 2D patch is required for input, a 3D patch is returned where the
3rd dimension corresponds to the source rows/columns. For the case of a
- single source, the [`Patch.squeeze`](`dascore.Patch.squeeze`) method
+ single source, the [`Patch.squeeze`](`dascore.proc.squeeze`) method
can be helpful to remove length 1 dimensions.
Parameters
diff --git a/dascore/proc/filter.py b/dascore/proc/filter.py
index 74b3f75a2..af3400d8d 100644
--- a/dascore/proc/filter.py
+++ b/dascore/proc/filter.py
@@ -552,7 +552,7 @@ def slope_filter(
>>> filt = np.array([2e3,2.2e3,8e3,2e4]) * dc.get_unit("m/s")
>>> patch_filtered = patch.slope_filter(filt=filt)
- The [FK recipe](`docs/recipes/fk.qmd`) provides additional examples.
+ The [FK recipe](/recipes/fk.qmd) provides additional examples.
"""
def _check_inputs(patch, filt, dims):
diff --git a/dascore/proc/hampel.py b/dascore/proc/hampel.py
index 8cb203402..ac8f38e2d 100644
--- a/dascore/proc/hampel.py
+++ b/dascore/proc/hampel.py
@@ -148,9 +148,8 @@ def hampel_filter(
- Installing `bottleneck` package can further improve approximate-mode
performance.
- See Also
- --------
- - [Despiking recipe](`docs/recipes/despiking.qmd`)
+ The [despiking recipe](/recipes/despiking.qmd) shows this filter used on
+ an example dataset.
Examples
--------
diff --git a/dascore/proc/mute.py b/dascore/proc/mute.py
index 9d9d4d9c8..4e58c7181 100644
--- a/dascore/proc/mute.py
+++ b/dascore/proc/mute.py
@@ -525,10 +525,10 @@ def line_mute(
See Also
--------
- - [`Patch.select`](`dascore.Patch.select`)
- - [`Patch.taper_range`](`dascore.Patch.taper_range`)
- - [`Patch.gaussian_filter`](`dascore.proc.filter.gaussian_filter`)
- - [`Patch.slope_mute`](`dascore.Patch.slope_mute`)
+ dascore.proc.coords.select
+ dascore.proc.taper.taper_range
+ dascore.proc.filter.gaussian_filter
+ dascore.proc.mute.slope_mute
"""
# Get geometry object to set up the problem.
geo = _get_mute_geometry(patch, kwargs, relative)
@@ -610,8 +610,8 @@ def slope_mute(
See Also
--------
- - [`Patch.slope_filter`](`dascore.proc.filter.slope_filter`)
- - [`Patch.line_mute`](`dascore.proc.mute.line_mute`)
+ dascore.proc.filter.slope_filter
+ dascore.proc.mute.line_mute
"""
# Convert slopes to array and validate
slopes_array = np.asarray(slopes)
diff --git a/dascore/proc/resample.py b/dascore/proc/resample.py
index d969744f6..8435a5c03 100644
--- a/dascore/proc/resample.py
+++ b/dascore/proc/resample.py
@@ -122,7 +122,7 @@ def interpolate(patch: PatchType, kind: str | int = "linear", **kwargs) -> Patch
This function just uses scipy's interp1d function under the hood.
See scipy.interpolate.interp1d for information.
- See also [snap](`dascore.core.Patch.snap_coords`).
+ See also [snap](`dascore.proc.snap_coords`).
Examples
--------
diff --git a/dascore/proc/whiten.py b/dascore/proc/whiten.py
index 531d9bc52..20be5793f 100644
--- a/dascore/proc/whiten.py
+++ b/dascore/proc/whiten.py
@@ -90,7 +90,7 @@ def whiten(
The whitened signal is returned in the same domain (eq frequency or
time domain) as the input signal. See also the
- [Whiten Processing Section](`docs/tutorial/processing.qmd`#whiten).
+ [Whiten Processing Section](/tutorial/processing.qmd#whiten).
Parameters
----------
diff --git a/dascore/transform/fourier.py b/dascore/transform/fourier.py
index b86402ab9..400b5c6da 100644
--- a/dascore/transform/fourier.py
+++ b/dascore/transform/fourier.py
@@ -262,12 +262,12 @@ def dft(
- Non-dimensional coordinates associated with transformed coordinates
will be dropped in the output.
- - See the [FFT notes](`docs/notes/dft_notes.qmd`) for more details.
+ - See the [FFT notes](/notes/dft_notes.qmd) for more details.
See Also
--------
- - [idft](`dascore.transform.fourier.idft`)
- - [stft](`dascore.transform.fourier.stft`)
+ dascore.transform.fourier.idft
+ dascore.transform.fourier.stft
Examples
--------
@@ -418,7 +418,7 @@ def idft(patch: PatchType, dim: str | Sequence[str] | None = None) -> PatchType:
Currently, only patches that have been transformed with
[dft](`dascore.transform.fourier.dft`) can be used with this function.
After transformation with dft, the transformed coordinates cannot change
- (e.g., with [select]('dascore.proc.basic.select`) otherwise idft won't
+ (e.g., with [select](`dascore.proc.select`)) otherwise idft won't
work.
Parameters
@@ -436,13 +436,12 @@ def idft(patch: PatchType, dim: str | Sequence[str] | None = None) -> PatchType:
- Real transforms are determined by transformed coordinates which have
no negative values.
- - See the [FFT note](dascore.org/notes/fft_notes.html) in Notes section
- of DASCore's documentation.
+ - See the [FFT note](/notes/dft_notes.qmd) for more details.
See Also
--------
- - [dft](`dascore.transform.fourier.dft`)
- - [istft](`dascore.transform.fourier.istft`)
+ dascore.transform.fourier.dft
+ dascore.transform.fourier.istft
Examples
--------
diff --git a/dascore/transform/integrate.py b/dascore/transform/integrate.py
index 17e1e13c0..30140dd4e 100644
--- a/dascore/transform/integrate.py
+++ b/dascore/transform/integrate.py
@@ -108,7 +108,7 @@ def integrate(
-----
The number of dimensions will always remain the same regardless of `definite`
value. To remove dimensions with length 1, use
- [`Patch.squeeze`](`dascore.Patch.squeeze`).
+ [`Patch.squeeze`](`dascore.proc.squeeze`).
Examples
--------
diff --git a/dascore/transform/strain.py b/dascore/transform/strain.py
index d5110dcdc..09b0ba05d 100644
--- a/dascore/transform/strain.py
+++ b/dascore/transform/strain.py
@@ -84,7 +84,7 @@ def velocity_to_strain_rate(
gauge_length is more complex with higher order filters. See
@yang2022filtering for more info.
- See the [`velocity_to_strain_rate` note](docs/notes/velocity_to_strain_rate.qmd)
+ See the [`velocity_to_strain_rate` note](/notes/velocity_to_strain_rate.qmd)
for more details on step_multiple and order effects.
The [edgeless](`dascore.Patch.velocity_to_strain_rate_edgeless`) version
@@ -166,7 +166,7 @@ def velocity_to_strain_rate_edgeless(
the sampling along the distance dimension.
See the
- [`velocity_to_strain_rate` note](docs/notes/velocity_to_strain_rate.qmd)
+ [`velocity_to_strain_rate` note](/notes/velocity_to_strain_rate.qmd)
for more details on step_multiple and order effects.
"""
if step_multiple <= 0:
diff --git a/dascore/utils/docs.py b/dascore/utils/docs.py
index 9802ab7cc..abbe5bcd2 100644
--- a/dascore/utils/docs.py
+++ b/dascore/utils/docs.py
@@ -2,8 +2,12 @@
from __future__ import annotations
+import importlib
import inspect
+import logging
import os
+import pkgutil
+import re
import textwrap
from collections.abc import Sequence
from pathlib import Path
@@ -165,3 +169,258 @@ def objs_to_doc_df(doc_dict, cross_reference=True):
df = pd.Series(out).to_frame().reset_index()
df.columns = ["Name", "Description"]
return df
+
+
+# ---------------------------------------------------------------------------
+# Rendering a module's public API onto a single documentation page.
+#
+# Modules of small helpers get one page each rather than a page per function:
+# the summary and signature stay visible while the parameter table, examples
+# and notes sit in a collapsed callout. Every entry keeps an explicit anchor so
+# cross references can link straight to it.
+# ---------------------------------------------------------------------------
+
+
+def get_doc_anchor(dotted: str) -> str:
+ """Return the html anchor used for a dotted path on a rendered API page."""
+ return dotted.replace(".", "-").replace("_", "-").lower()
+
+
+def _fmt_annotation(anno) -> str:
+ """Render an annotation the way it was written in the source."""
+ if isinstance(anno, str):
+ return anno
+ return getattr(anno, "__name__", None) or str(anno).replace("typing.", "")
+
+
+def _fmt_default(value) -> str:
+ """Render a default value compactly (classes by name, not repr)."""
+ if inspect.isclass(value) or inspect.isfunction(value):
+ return value.__name__
+ return repr(value)
+
+
+def _signature(obj) -> str:
+ """Render a signature without the quoting artifacts of string annotations."""
+ sig = inspect.signature(obj)
+ parts, seen_kw_only = [], False
+ for p in sig.parameters.values():
+ if p.kind is p.KEYWORD_ONLY and not seen_kw_only:
+ parts.append("*")
+ seen_kw_only = True
+ text = p.name
+ if p.kind is p.VAR_POSITIONAL:
+ text = f"*{text}"
+ elif p.kind is p.VAR_KEYWORD:
+ text = f"**{text}"
+ if p.annotation is not p.empty:
+ text += f": {_fmt_annotation(p.annotation)}"
+ if p.default is not p.empty:
+ text += f" = {_fmt_default(p.default)}"
+ parts.append(text)
+ ret = ""
+ if sig.return_annotation is not sig.empty:
+ ret = f" -> {_fmt_annotation(sig.return_annotation)}"
+ return f"{obj.__name__}({', '.join(parts)}){ret}"
+
+
+def _param_types(obj) -> dict[str, str]:
+ """Map parameter name to its annotation, for the parameter table."""
+ sig = inspect.signature(obj)
+ return {
+ p.name: _fmt_annotation(p.annotation)
+ for p in sig.parameters.values()
+ if p.annotation is not p.empty
+ }
+
+
+def _parse(doc: str):
+ """Parse a numpydoc docstring into griffe sections."""
+ from dascore.utils.misc import optional_import # noqa: PLC0415
+
+ # griffe ships with the doc build rather than with dascore.
+ griffe = optional_import("griffe", required_for="rendering the API docs")
+
+ # Griffe logs a warning for every parameter documented without a type,
+ # which is DASCore's house style (types come from the annotations).
+ logging.getLogger("griffe").setLevel(logging.ERROR)
+ return griffe.Docstring(doc, parser="numpy").parse("numpy")
+
+
+def _fence(text: str, char: str = "`") -> str:
+ """
+ Return a fence long enough to enclose text carrying fences of its own.
+
+ Docstrings are markdown, so one may hold a code block or a callout; a
+ fence of the usual three would be closed by the first one inside and the
+ rest of the page would render as its contents.
+ """
+ longest = max((len(m) for m in re.findall(f"{char}+", text)), default=0)
+ return char * max(3, longest + 1)
+
+
+def _render_sections(sections, types: dict[str, str]) -> tuple[str, list[str]]:
+ """Return the summary line and the collapsible body blocks."""
+ summary, blocks = "", []
+ for sec in sections:
+ kind = sec.kind.value
+ if kind == "text":
+ text = str(sec.value).strip()
+ if not summary: # the first line of the first block is the summary
+ first, _, rest = text.partition("\n\n")
+ summary = " ".join(first.split())
+ text = rest.strip()
+ if text:
+ blocks.append(text)
+ elif kind == "parameters":
+ rows = ["| Parameter | Type | Description |", "|---|---|---|"]
+ for p in sec.value:
+ anno = types.get(p.name) or (
+ "" if p.annotation is None else str(p.annotation)
+ )
+ anno = f"`{anno}`" if anno else ""
+ desc = " ".join(str(p.description).split())
+ rows.append(f"| `{p.name}` | {anno} | {desc} |")
+ blocks.append("\n".join(rows))
+ elif kind in {"returns", "yields"}:
+ items = []
+ for r in sec.value:
+ anno = "" if r.annotation is None else f"`{r.annotation}` — "
+ items.append(f"{anno}{' '.join(str(r.description).split())}")
+ blocks.append(f"**{kind.title()}:** " + "; ".join(items))
+ elif kind == "raises":
+ items = [
+ f"`{r.annotation}` — {' '.join(str(r.description).split())}"
+ for r in sec.value
+ ]
+ blocks.append("**Raises:** " + "; ".join(items))
+ elif kind == "examples":
+ for _, text in sec.value:
+ body = str(text).strip()
+ # An example which is already a code block keeps its own
+ # fence; wrapping it would show the fence as content. A
+ # `{python}` cell is demoted to a plain block, since the page
+ # displays examples rather than running them.
+ if body.startswith("```"):
+ blocks.append(re.sub(r"^(`{3,})\{python\}", r"\1python", body))
+ else:
+ fence = _fence(body)
+ blocks.append(f"{fence}python\n{body}\n{fence}")
+ elif kind in {"attributes", "other parameters"}:
+ label = "Attribute" if kind == "attributes" else "Parameter"
+ rows = [f"| {label} | Type | Description |", "|---|---|---|"]
+ for a in sec.value:
+ anno = types.get(a.name) or (
+ "" if a.annotation is None else str(a.annotation)
+ )
+ anno = f"`{anno}`" if anno else ""
+ desc = " ".join(str(a.description).split())
+ rows.append(f"| `{a.name}` | {anno} | {desc} |")
+ blocks.append("\n".join(rows))
+ elif kind == "admonition":
+ body = getattr(sec.value, "description", sec.value)
+ blocks.append(f"**{sec.title or 'Note'}:** {str(body).strip()}")
+ # Any other section is dropped rather than rendered: griffe parses
+ # several into models of their own, whose repr on the page would be
+ # worse than their absence.
+ return summary, blocks
+
+
+def iter_public(module_name: str):
+ """
+ Yield (name, object) for public objects defined in the module.
+
+ Decorated helpers (e.g. functools.cache) are callables rather than plain
+ functions, so unwrap before deciding what a name is and where it was
+ defined; skipping them would drop both their docs and their anchor.
+ """
+ mod = importlib.import_module(module_name)
+ for name in sorted(dir(mod)):
+ if name.startswith("_"):
+ continue
+ obj = getattr(mod, name)
+ unwrapped = inspect.unwrap(obj)
+ is_documentable = (
+ inspect.isfunction(unwrapped)
+ or inspect.isclass(unwrapped)
+ or (callable(obj) and inspect.isroutine(unwrapped))
+ )
+ if not is_documentable or inspect.ismodule(obj):
+ continue
+ if getattr(unwrapped, "__module__", "") != module_name:
+ continue
+ yield name, obj
+
+
+def render_module_api(module_name: str) -> str:
+ """Render every public object defined in a module as markdown."""
+ out: list[str] = []
+ for name, obj in iter_public(module_name):
+ dotted = f"{module_name}.{name}"
+ doc = inspect.getdoc(obj) or ""
+ summary, blocks = (
+ _render_sections(_parse(doc), _param_types(obj)) if doc else ("", [])
+ )
+ out.append(f"#### {name} {{#{get_doc_anchor(dotted)}}}\n")
+ out.append(f"```python\n{_signature(obj)}\n```\n")
+ if summary:
+ out.append(f"{summary}\n")
+ if blocks:
+ fence = _fence("\n".join(blocks), ":")
+ out.append(
+ f'{fence} {{.callout-note collapse="true" appearance="simple" '
+ 'title="Details"}\n'
+ )
+ out.extend(b + "\n" for b in blocks)
+ out.append(f"{fence}\n")
+ return "\n".join(out)
+
+
+def iter_package_modules(package_name: str):
+ """
+ Yield the importable name of a package and each public module in it.
+
+ The package itself is included: a helper defined in its ``__init__`` is
+ as public as one in a submodule, and leaving it out would document
+ neither it nor an anchor to link to it.
+ """
+ package = importlib.import_module(package_name)
+ yield package_name
+ for info in pkgutil.walk_packages(package.__path__, prefix=f"{package_name}."):
+ if not any(part.startswith("_") for part in info.name.split(".")):
+ yield info.name
+
+
+def render_package_api(package_name: str, skip_empty: bool = True) -> str:
+ """
+ Render the public API of every module in a package as markdown.
+
+ Each module becomes a level-two heading and each object a level-three
+ heading below it, so the page table of contents lists modules while every
+ object still has an anchor to link to.
+
+ Parameters
+ ----------
+ package_name
+ The importable name of the package, e.g. "dascore.utils".
+ skip_empty
+ If True, omit modules which define no public objects.
+ """
+ out = []
+ for name in iter_package_modules(package_name):
+ try:
+ module = importlib.import_module(name)
+ except ImportError: # an optional dependency the doc env lacks
+ continue
+ # Rendering is deliberately outside the guard: a module which cannot
+ # be imported is skipped, but a renderer which cannot run would
+ # otherwise empty the page without saying so.
+ body = render_module_api(name)
+ if skip_empty and not body.strip():
+ continue
+ out.append(f"### {name} {{#{get_doc_anchor(name)}}}\n")
+ module_doc = inspect.getdoc(module) or ""
+ if module_doc:
+ out.append(" ".join(module_doc.split("\n\n")[0].split()) + "\n")
+ out.append(body)
+ return "\n".join(out)
diff --git a/dascore/utils/patch.py b/dascore/utils/patch.py
index 8955d26bf..4c4a1bd0c 100644
--- a/dascore/utils/patch.py
+++ b/dascore/utils/patch.py
@@ -301,7 +301,7 @@ def patch_function(
machinery multiple times from within another patch function.
- If using `PatchType` or `SpoolType` type variables from the
- [constants module](`dascore.constants`), make sure dascore is imported
+ [constants module](/reference/index.qmd), make sure dascore is imported
as dc at the top of the file where the patch function is defined so
the forward refs can be resolved properly for type checking.
"""
@@ -602,8 +602,8 @@ def get_patch_names(
See Also
--------
- - [`Patch.get_patch_name`](`dascore.Patch.get_patch_name`)
- - [`Spool.get_patch_names`](`dascore.BaseSpool.get_patch_names`)
+ dascore.core.patch.Patch.get_patch_name
+ dascore.core.spool.BaseSpool.get_patch_names
Examples
--------
@@ -1376,7 +1376,7 @@ def concatenate_patches(
- [`Spool.chunk`](`dascore.BaseSpool.chunk`) performs a similar operation
but accounts for coordinate values.
- See also the
- [chunk section of the spool tutorial](`docs/tutorial/spool`#concatenate)
+ [chunk section of the spool tutorial](/tutorial/spool.qmd#concatenate)
"""
def _get_dim_and_value(kwargs):
diff --git a/docs/.gitignore b/docs/.gitignore
deleted file mode 100644
index 8088b9c47..000000000
--- a/docs/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.quarto/
-_site/*
-
-/.quarto/
-
-**/*.quarto_ipynb
diff --git a/docs/_publish.yml b/docs/_publish.yml
deleted file mode 100644
index 9ffd03714..000000000
--- a/docs/_publish.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-- source: project
- netlify:
- - id: da79b12f-cb25-4fcc-aeb6-f19705848130
- url: 'https://dascore.netlify.app'
diff --git a/docs/changelog.qmd b/docs/changelog.qmd
deleted file mode 100644
index 3b246252d..000000000
--- a/docs/changelog.qmd
+++ /dev/null
@@ -1,5 +0,0 @@
-# Changelog
-
-DASCore's changelog is the [releases page](https://github.com/DASDAE/dascore/releases), which tracks changes from one version to another.
-
-This page remains only so that existing links to it keep working; it is not part of the site navigation and is never updated per pull request. Each pull request describes its own user-facing and breaking changes, and those descriptions are collected into the release notes when a version is tagged. See [publish a new release](contributing/publish_a_new_release.qmd) for that workflow.
diff --git a/docs/filters/fill_links.py b/docs/filters/fill_links.py
deleted file mode 100644
index ad819cb41..000000000
--- a/docs/filters/fill_links.py
+++ /dev/null
@@ -1,113 +0,0 @@
-"""
-Custom filter that makes dynamic (sphinx-esc) cross links work.
-
-Replaces markdown such as [Patch](`dascore.Patch`) with the path to the markdown
-file created from dascore/scripts/build_api_docs.py.
-"""
-
-from __future__ import annotations
-
-import io
-import json
-import re
-import sys
-from functools import cache
-from pathlib import Path
-
-
-@cache
-def get_cross_ref_dict() -> dict[str, str]:
- """Load cross-reference dictionaries."""
- out = {}
- path = Path(__file__).absolute()
- count = 0
- while path.name != "docs" or str(path).count("docs") != 1:
- path = path.parent
- count += 1
- if count > 100:
- raise ValueError("failed to find cross-ref file")
- for cross_ref_path in path.rglob(".cross_ref.json"):
- out.update(json.loads(cross_ref_path.read_text()))
- assert out, "didn't find cross ref dict"
- return out
-
-
-def load_stdin(input_stream=None):
- """Load input from stdin (json string)."""
- if input_stream is None:
- input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8")
- return input_stream.read()
-
-
-def dump_stdout(output_str, out_stream=None):
- """Dump modified json string to std out."""
- if out_stream is None:
- out_stream = sys.stdout
- out_stream.write(output_str)
-
-
-def replace_links(json_data, raw_string):
- """Replace the aliases (links) with the cross-references."""
-
- def _yield_links(element):
- """Yield all link elements."""
- if isinstance(element, list):
- for sub_element in element:
- yield from _yield_links(sub_element)
- elif isinstance(element, dict):
- if element.get("t") == "Link":
- yield element
- else:
- for item, value in element.items():
- yield from _yield_links(value)
- # yield from _yield_links(element.get("c", []))
-
- # reg_pattern = r'(?<="\%60)(.*?)(?=\%60")'
- reg_pattern = r"(?<=\%60)(.*?)(?=\%60)"
- replace_dict = {}
- for link in _yield_links(json_data):
- str_to_scan = json.dumps(link, separators=(",", ":"), ensure_ascii=False)
- matches = re.finditer(reg_pattern, str_to_scan)
- for match in matches:
- start, stop = match.span()
- cross_refs = get_cross_ref_dict()
- key = str_to_scan[start:stop].replace("`", "")
- if (new_value := cross_refs.get(key, key)) != key:
- # new_sub_str = str_to_scan.replace(f'"%60{key}%60"', f'"{new_value}"')
- new_sub_str = str_to_scan.replace(f"%60{key}%60", f"{new_value}")
- if str_to_scan in replace_dict:
- assert replace_dict[str_to_scan] == new_sub_str
- replace_dict[str_to_scan] = new_sub_str
- for i, v in replace_dict.items():
- raw_string = raw_string.replace(i, v)
- return raw_string
-
-
-def test():
- """Function to test filter."""
- here = Path(__file__).parent
- data_path = here / "filter_test_data" / "test_data_4.json"
- with data_path.open("r") as fi:
- data = fi.read()
- input_stream = io.StringIO(data)
- main(input_stream)
-
-
-def main(raw_data=None):
- """Run filter."""
- raw_str = load_stdin(raw_data)
- json_data = json.loads(raw_str)
- output_data = replace_links(json_data, raw_str)
- dump_stdout(output_data)
-
-
-if __name__ == "__main__":
- main()
- # test()
-
-
-# This is useful for debugging, but need to first install remote_pdb
-# from remote_pdb import RemotePdb
-# RemotePdb('127.0.0.1', 4444).set_trace()
-# Then telnet into the debugger
-# telnet 127.0.0.1 4444
diff --git a/docs/filters/filter_test_data/test.json b/docs/filters/filter_test_data/test.json
deleted file mode 100644
index af852cac2..000000000
--- a/docs/filters/filter_test_data/test.json
+++ /dev/null
@@ -1 +0,0 @@
-{"pandoc-api-version":[1,22,2,1],"meta":{"biblio-config":{"t":"MetaBool","c":true},"bibliography":{"t":"MetaList","c":[{"t":"MetaInlines","c":[{"t":"Str","c":"references.bib"}]}]},"code-copy":{"t":"MetaBool","c":true},"date-format":{"t":"MetaInlines","c":[{"t":"Str","c":"long"}]},"document-css":{"t":"MetaBool","c":false},"fig-responsive":{"t":"MetaBool","c":true},"header-includes":{"t":"MetaList","c":[{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html",""]}]}]},"include-after":{"t":"MetaList","c":[{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html"," \n"]}]},{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html","\n"]}]},{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html"," \n"]}]}]},"include-before":{"t":"MetaList","c":[{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html","
\n \n \n\n\n
\n\n\n
\n \n
\n\n\n"]}]}]},"labels":{"t":"MetaMap","c":{"abstract":{"t":"MetaInlines","c":[{"t":"Str","c":"Abstract"}]},"affiliations":{"t":"MetaInlines","c":[{"t":"Str","c":"Affiliations"}]},"authors":{"t":"MetaInlines","c":[{"t":"Str","c":"Authors"}]},"description":{"t":"MetaInlines","c":[{"t":"Str","c":"Description"}]},"doi":{"t":"MetaInlines","c":[{"t":"Str","c":"Doi"}]},"modified":{"t":"MetaInlines","c":[{"t":"Str","c":"Modified"}]},"published":{"t":"MetaInlines","c":[{"t":"Str","c":"Published"}]}}},"lang":{"t":"MetaInlines","c":[{"t":"Str","c":"en"}]},"link-citations":{"t":"MetaBool","c":true},"quarto-template-params":{"t":"MetaMap","c":{"title-block-categories":{"t":"MetaInlines","c":[{"t":"Str","c":"true"}]}}},"quarto-version":{"t":"MetaInlines","c":[{"t":"Str","c":"1.3.119"}]},"theme":{"t":"MetaMap","c":{"light":{"t":"MetaInlines","c":[{"t":"Str","c":"yeti"}]}}},"title":{"t":"MetaInlines","c":[{"t":"Str","c":"Quickstart"}]},"toc-title":{"t":"MetaInlines","c":[{"t":"Str","c":"On"},{"t":"Space"},{"t":"Str","c":"this"},{"t":"Space"},{"t":"Str","c":"page"}]}},"blocks":[{"t":"Para","c":[{"t":"Str","c":"dascore"},{"t":"Space"},{"t":"Str","c":"can"},{"t":"Space"},{"t":"Link","c":[["",[],[]],[{"t":"Str","c":"link"}],["%60dascore.Patch%60",""]]}]},{"t":"Header","c":[2,["read-a-single-file",[],[]],[{"t":"Str","c":"Read"},{"t":"Space"},{"t":"Str","c":"a"},{"t":"Space"},{"t":"Str","c":"single"},{"t":"Space"},{"t":"Str","c":"file"}]]},{"t":"Div","c":[["",["cell"],[["execution_count","1"]]],[{"t":"CodeBlock","c":[["",["python","cell-code"],[]],"import dascore as dc\nfrom dascore.utils.downloader import fetch\n# get a path to an example file, replace with your path\nfile_path = fetch('terra15_das_1_trimmed.hdf5')\nspool = dc.spool(file_path)"]}]]},{"t":"Header","c":[2,["read-data-from-a-directory-of-fiber-optic-data-files",[],[]],[{"t":"Str","c":"Read"},{"t":"Space"},{"t":"Str","c":"data"},{"t":"Space"},{"t":"Str","c":"from"},{"t":"Space"},{"t":"Str","c":"a"},{"t":"Space"},{"t":"Str","c":"directory"},{"t":"Space"},{"t":"Str","c":"of"},{"t":"Space"},{"t":"Str","c":"fiber"},{"t":"Space"},{"t":"Str","c":"optic"},{"t":"Space"},{"t":"Str","c":"data"},{"t":"Space"},{"t":"Str","c":"files"}]]},{"t":"Div","c":[["",["cell"],[["execution_count","2"]]],[{"t":"CodeBlock","c":[["",["python","cell-code"],[]],"import dascore as dc\nfrom dascore.utils.downloader import fetch\n# get a path to a directory of das files, replace with your path\ndirectory_path = fetch('terra15_das_1_trimmed.hdf5').parent\nspool = dc.spool(directory_path).update()"]}]]},{"t":"Header","c":[2,["get-patches-2d-array",[],[]],[{"t":"Str","c":"Get"},{"t":"Space"},{"t":"Str","c":"patches"},{"t":"Space"},{"t":"Str","c":"(2D"},{"t":"Space"},{"t":"Str","c":"array)"}]]},{"t":"Div","c":[["",["cell"],[["execution_count","3"]]],[{"t":"CodeBlock","c":[["",["python","cell-code"],[]],"import dascore as dc\nspool = dc.get_example_spool('diverse_das')\n# get patches through iteration\nfor patch in spool:\n ...\n# Or through indexing\npatch = spool[0]"]}]]},{"t":"Header","c":[2,["perform-processing",[],[]],[{"t":"Str","c":"Perform"},{"t":"Space"},{"t":"Str","c":"processing"}]]},{"t":"Div","c":[["",["cell"],[["execution_count","4"]]],[{"t":"CodeBlock","c":[["",["python","cell-code"],[]],"import dascore as dc\npatch = dc.get_example_patch('random_das')\nout = (\n patch.decimate(time=8) # decimate along time axis\n .detrend(dim='distance') # detrend in distance axis\n .pass_filter(time=(None, 10)) # apply bandpass filter\n)"]}]]},{"t":"Header","c":[2,["visualize",[],[]],[{"t":"Str","c":"Visualize"}]]},{"t":"Div","c":[["",["cell"],[["execution_count","5"]]],[{"t":"CodeBlock","c":[["",["python","cell-code"],[]],"import dascore as dc\npatch = dc.get_example_patch('random_das')\npatch.viz.waterfall(show=True, scale=0.02);"]},{"t":"Div","c":[["",["cell-output","cell-output-display"],[]],[{"t":"Para","c":[{"t":"Image","c":[["",[],[["width","592"],["height","419"]]],[],["quickstart_files/figure-html/cell-6-output-1.png",""]]}]}]]}]]},{"t":"Div","c":[["quarto-navigation-envelope",["hidden"],[]],[{"t":"Para","c":[{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar-title"]]],[{"t":"Str","c":"DASCore"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar-title"]]],[{"t":"Str","c":"DASCore"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Introduction"]]],[{"t":"Str","c":"Introduction"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Tutorial"]]],[{"t":"Str","c":"Tutorial"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Recipes"]]],[{"t":"Str","c":"Recipes"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Contributing"]]],[{"t":"Str","c":"Contributing"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:API"]]],[{"t":"Str","c":"API"}]]}]}]]},{"t":"Div","c":[["quarto-meta-markdown",["hidden"],[]],[{"t":"Para","c":[{"t":"Span","c":[["",["hidden"],[["render-id","quarto-metatitle"]]],[{"t":"Str","c":"DASCore"},{"t":"Space"},{"t":"Str","c":"-"},{"t":"Space"},{"t":"Str","c":"Quickstart"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-twittercardtitle"]]],[{"t":"Str","c":"DASCore"},{"t":"Space"},{"t":"Str","c":"-"},{"t":"Space"},{"t":"Str","c":"Quickstart"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-ogcardtitle"]]],[{"t":"Str","c":"DASCore"},{"t":"Space"},{"t":"Str","c":"-"},{"t":"Space"},{"t":"Str","c":"Quickstart"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-metasitename"]]],[{"t":"Str","c":"DASCore"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-twittercarddesc"]]],[]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-ogcardddesc"]]],[]]}]}]]}]}
diff --git a/docs/filters/filter_test_data/test_data_2.json b/docs/filters/filter_test_data/test_data_2.json
deleted file mode 100644
index 5964df327..000000000
--- a/docs/filters/filter_test_data/test_data_2.json
+++ /dev/null
@@ -1 +0,0 @@
-{"pandoc-api-version":[1,22,2,1],"meta":{"biblio-config":{"t":"MetaBool","c":true},"bibliography":{"t":"MetaList","c":[{"t":"MetaInlines","c":[{"t":"Str","c":"../../../../../references.bib"}]}]},"code-copy":{"t":"MetaBool","c":true},"date-format":{"t":"MetaInlines","c":[{"t":"Str","c":"long"}]},"document-css":{"t":"MetaBool","c":false},"fig-responsive":{"t":"MetaBool","c":true},"header-includes":{"t":"MetaList","c":[{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html",""]}]}]},"include-after":{"t":"MetaList","c":[{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html"," \n"]}]},{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html","\n"]}]},{"t":"MetaBlocks","c":[{"t":"RawBlock","c":["html","\n
\n\n\n"]}]}]},"labels":{"t":"MetaMap","c":{"abstract":{"t":"MetaInlines","c":[{"t":"Str","c":"Abstract"}]},"affiliations":{"t":"MetaInlines","c":[{"t":"Str","c":"Affiliations"}]},"authors":{"t":"MetaInlines","c":[{"t":"Str","c":"Authors"}]},"description":{"t":"MetaInlines","c":[{"t":"Str","c":"Description"}]},"doi":{"t":"MetaInlines","c":[{"t":"Str","c":"Doi"}]},"modified":{"t":"MetaInlines","c":[{"t":"Str","c":"Modified"}]},"published":{"t":"MetaInlines","c":[{"t":"Str","c":"Published"}]}}},"lang":{"t":"MetaInlines","c":[{"t":"Str","c":"en"}]},"link-citations":{"t":"MetaBool","c":true},"pagetitle":{"t":"MetaString","c":"integrate"},"quarto-custom-nodes":{"t":"MetaList","c":[{"t":"MetaMap","c":{"id":{"t":"MetaString","c":"Callout 1 Block"},"kind":{"t":"MetaString","c":"Block"},"t":{"t":"MetaString","c":"Callout"},"tbl":{"t":"MetaMap","c":{"attr":{"t":"MetaMap","c":{"attributes":{"t":"MetaMap","c":{}},"classes":{"t":"MetaList","c":[]},"identifier":{"t":"MetaString","c":""}}},"content":{"t":"MetaBlocks","c":[{"t":"Para","c":[{"t":"Str","c":"To"},{"t":"Space"},{"t":"Str","c":"remove"},{"t":"Space"},{"t":"Str","c":"dimensions"},{"t":"Space"},{"t":"Str","c":"with"},{"t":"Space"},{"t":"Str","c":"length"},{"t":"Space"},{"t":"Str","c":"1,"},{"t":"SoftBreak"},{"t":"Str","c":"see"},{"t":"Space"},{"t":"Link","c":[["",[],[]],[{"t":"Str","c":"squeeze"}],["%60dascore.proc.basic.squeeze%60",""]]},{"t":"Str","c":"."}]}]}}}}}]},"quarto-template-params":{"t":"MetaMap","c":{"title-block-categories":{"t":"MetaInlines","c":[{"t":"Str","c":"true"}]}}},"quarto-version":{"t":"MetaInlines","c":[{"t":"Str","c":"1.3.450"}]},"theme":{"t":"MetaMap","c":{"dark":{"t":"MetaInlines","c":[{"t":"Str","c":"darkly"}]},"light":{"t":"MetaInlines","c":[{"t":"Str","c":"yeti"}]}}},"toc-title":{"t":"MetaInlines","c":[{"t":"Str","c":"On"},{"t":"Space"},{"t":"Str","c":"this"},{"t":"Space"},{"t":"Str","c":"page"}]}},"blocks":[{"t":"Header","c":[1,["integrate",[],[]],[{"t":"Str","c":"integrate"}]]},{"t":"Plain","c":[{"t":"RawInline","c":["QUARTO_custom","Callout 1 Block"]}]},{"t":"Div","c":[["quarto-navigation-envelope",["hidden"],[]],[{"t":"Para","c":[{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar-title"]]],[{"t":"Str","c":"API"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar-title"]]],[{"t":"Str","c":"DASCore"},{"t":"Space"},{"t":"Str","c":"(0.1.1.dev2)"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-next"]]],[{"t":"Str","c":"spectro"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-prev"]]],[{"t":"Str","c":"integrate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-1"]]],[{"t":"Str","c":"dascore"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/version.html"]]],[{"t":"Str","c":"version"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-2"]]],[{"t":"Str","c":"clients"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-3"]]],[{"t":"Str","c":"dirspool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-4"]]],[{"t":"Str","c":"DirectorySpool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/clients/dirspool/DirectorySpool/get_contents.html"]]],[{"t":"Str","c":"get_contents"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/clients/dirspool/DirectorySpool/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-5"]]],[{"t":"Str","c":"filespool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-6"]]],[{"t":"Str","c":"FileSpool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/clients/filespool/FileSpool/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-7"]]],[{"t":"Str","c":"compat"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/compat/array.html"]]],[{"t":"Str","c":"array"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-8"]]],[{"t":"Str","c":"constants"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-9"]]],[{"t":"Str","c":"ExecutorType"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/constants/ExecutorType/map.html"]]],[{"t":"Str","c":"map"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-10"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/schema.html"]]],[{"t":"Str","c":"schema"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-11"]]],[{"t":"Str","c":"attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/check_coords.html"]]],[{"t":"Str","c":"check_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/check_dims.html"]]],[{"t":"Str","c":"check_dims"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/combine_patch_attrs.html"]]],[{"t":"Str","c":"combine_patch_attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/decompose_attrs.html"]]],[{"t":"Str","c":"decompose_attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/merge_compatible_coords_attrs.html"]]],[{"t":"Str","c":"merge_compatible_coords_attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-12"]]],[{"t":"Str","c":"PatchAttrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/coords_from_dims.html"]]],[{"t":"Str","c":"coords_from_dims"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/drop_private.html"]]],[{"t":"Str","c":"drop_private"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/flat_dump.html"]]],[{"t":"Str","c":"flat_dump"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/from_dict.html"]]],[{"t":"Str","c":"from_dict"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/get.html"]]],[{"t":"Str","c":"get"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/items.html"]]],[{"t":"Str","c":"items"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/parse_coord_attributes.html"]]],[{"t":"Str","c":"parse_coord_attributes"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/rename_dimension.html"]]],[{"t":"Str","c":"rename_dimension"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/attrs/PatchAttrs/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-13"]]],[{"t":"Str","c":"coordmanager"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/get_coord_manager.html"]]],[{"t":"Str","c":"get_coord_manager"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/merge_coord_managers.html"]]],[{"t":"Str","c":"merge_coord_managers"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-14"]]],[{"t":"Str","c":"CoordManager"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/convert_units.html"]]],[{"t":"Str","c":"convert_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/coord_range.html"]]],[{"t":"Str","c":"coord_range"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/coord_size.html"]]],[{"t":"Str","c":"coord_size"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/decimate.html"]]],[{"t":"Str","c":"decimate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/disassociate_coord.html"]]],[{"t":"Str","c":"disassociate_coord"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/drop_coords.html"]]],[{"t":"Str","c":"drop_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/drop_disassociated_coords.html"]]],[{"t":"Str","c":"drop_disassociated_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/equals.html"]]],[{"t":"Str","c":"equals"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/get_array.html"]]],[{"t":"Str","c":"get_array"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/get_coord.html"]]],[{"t":"Str","c":"get_coord"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/get_coord_tuple_map.html"]]],[{"t":"Str","c":"get_coord_tuple_map"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/keys.html"]]],[{"t":"Str","c":"keys"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/max.html"]]],[{"t":"Str","c":"max"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/min.html"]]],[{"t":"Str","c":"min"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/new.html"]]],[{"t":"Str","c":"new"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/rename_coord.html"]]],[{"t":"Str","c":"rename_coord"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/set_dims.html"]]],[{"t":"Str","c":"set_dims"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/set_units.html"]]],[{"t":"Str","c":"set_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/simplify_units.html"]]],[{"t":"Str","c":"simplify_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/snap.html"]]],[{"t":"Str","c":"snap"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/sort.html"]]],[{"t":"Str","c":"sort"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/squeeze.html"]]],[{"t":"Str","c":"squeeze"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/step.html"]]],[{"t":"Str","c":"step"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/to_summary_dict.html"]]],[{"t":"Str","c":"to_summary_dict"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/transpose.html"]]],[{"t":"Str","c":"transpose"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/update_from_attrs.html"]]],[{"t":"Str","c":"update_from_attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coordmanager/CoordManager/validate_data.html"]]],[{"t":"Str","c":"validate_data"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-15"]]],[{"t":"Str","c":"coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/ensure_consistent_dtype.html"]]],[{"t":"Str","c":"ensure_consistent_dtype"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/get_compatible_values.html"]]],[{"t":"Str","c":"get_compatible_values"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/get_coord.html"]]],[{"t":"Str","c":"get_coord"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-16"]]],[{"t":"Str","c":"BaseCoord"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/check_time_units.html"]]],[{"t":"Str","c":"check_time_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/convert_units.html"]]],[{"t":"Str","c":"convert_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/coord_range.html"]]],[{"t":"Str","c":"coord_range"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/empty.html"]]],[{"t":"Str","c":"empty"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/get_attrs_dict.html"]]],[{"t":"Str","c":"get_attrs_dict"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/get_next_index.html"]]],[{"t":"Str","c":"get_next_index"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/get_sample_count.html"]]],[{"t":"Str","c":"get_sample_count"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/get_slice_tuple.html"]]],[{"t":"Str","c":"get_slice_tuple"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/index.html"]]],[{"t":"Str","c":"index"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/max.html"]]],[{"t":"Str","c":"max"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/min.html"]]],[{"t":"Str","c":"min"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/set_units.html"]]],[{"t":"Str","c":"set_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/simplify_units.html"]]],[{"t":"Str","c":"simplify_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/snap.html"]]],[{"t":"Str","c":"snap"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/sort.html"]]],[{"t":"Str","c":"sort"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/to_summary.html"]]],[{"t":"Str","c":"to_summary"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/update_data.html"]]],[{"t":"Str","c":"update_data"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/BaseCoord/update_limits.html"]]],[{"t":"Str","c":"update_limits"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-17"]]],[{"t":"Str","c":"CoordArray"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordArray/check_time_units.html"]]],[{"t":"Str","c":"check_time_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordArray/convert_units.html"]]],[{"t":"Str","c":"convert_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordArray/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordArray/snap.html"]]],[{"t":"Str","c":"snap"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordArray/sort.html"]]],[{"t":"Str","c":"sort"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordArray/update_limits.html"]]],[{"t":"Str","c":"update_limits"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-18"]]],[{"t":"Str","c":"CoordDegenerate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordDegenerate/check_time_units.html"]]],[{"t":"Str","c":"check_time_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordDegenerate/empty.html"]]],[{"t":"Str","c":"empty"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordDegenerate/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordDegenerate/snap.html"]]],[{"t":"Str","c":"snap"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-19"]]],[{"t":"Str","c":"CoordMonotonicArray"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordMonotonicArray/check_time_units.html"]]],[{"t":"Str","c":"check_time_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordMonotonicArray/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-20"]]],[{"t":"Str","c":"CoordRange"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordRange/change_length.html"]]],[{"t":"Str","c":"change_length"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordRange/check_time_units.html"]]],[{"t":"Str","c":"check_time_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordRange/convert_units.html"]]],[{"t":"Str","c":"convert_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordRange/ensure_all_attrs_set.html"]]],[{"t":"Str","c":"ensure_all_attrs_set"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordRange/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordRange/sort.html"]]],[{"t":"Str","c":"sort"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordRange/update_limits.html"]]],[{"t":"Str","c":"update_limits"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-21"]]],[{"t":"Str","c":"CoordSummary"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordSummary/get_correct_dtype_cast_values.html"]]],[{"t":"Str","c":"get_correct_dtype_cast_values"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordSummary/ser_model.html"]]],[{"t":"Str","c":"ser_model"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/coords/CoordSummary/to_coord.html"]]],[{"t":"Str","c":"to_coord"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-22"]]],[{"t":"Str","c":"patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-23"]]],[{"t":"Str","c":"Patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/patch/Patch/assign_coords.html"]]],[{"t":"Str","c":"assign_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/patch/Patch/iresample.html"]]],[{"t":"Str","c":"iresample"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/patch/Patch/iselect.html"]]],[{"t":"Str","c":"iselect"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-24"]]],[{"t":"Str","c":"spool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/MemorySpool.html"]]],[{"t":"Str","c":"MemorySpool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/spool.html"]]],[{"t":"Str","c":"spool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-25"]]],[{"t":"Str","c":"BaseSpool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/chunk.html"]]],[{"t":"Str","c":"chunk"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/get_contents.html"]]],[{"t":"Str","c":"get_contents"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/map.html"]]],[{"t":"Str","c":"map"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/sort.html"]]],[{"t":"Str","c":"sort"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/split.html"]]],[{"t":"Str","c":"split"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/stack.html"]]],[{"t":"Str","c":"stack"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/BaseSpool/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-26"]]],[{"t":"Str","c":"DataFrameSpool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/DataFrameSpool/chunk.html"]]],[{"t":"Str","c":"chunk"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/DataFrameSpool/get_contents.html"]]],[{"t":"Str","c":"get_contents"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/DataFrameSpool/new_from_df.html"]]],[{"t":"Str","c":"new_from_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/DataFrameSpool/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/DataFrameSpool/sort.html"]]],[{"t":"Str","c":"sort"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/core/spool/DataFrameSpool/split.html"]]],[{"t":"Str","c":"split"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-27"]]],[{"t":"Str","c":"examples"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/dispersion_event.html"]]],[{"t":"Str","c":"dispersion_event"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/diverse_spool.html"]]],[{"t":"Str","c":"diverse_spool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/example_event_1.html"]]],[{"t":"Str","c":"example_event_1"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/example_event_2.html"]]],[{"t":"Str","c":"example_event_2"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/get_example_patch.html"]]],[{"t":"Str","c":"get_example_patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/get_example_spool.html"]]],[{"t":"Str","c":"get_example_spool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/patch_with_null.html"]]],[{"t":"Str","c":"patch_with_null"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/random_patch.html"]]],[{"t":"Str","c":"random_patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/random_patch_lat_lon.html"]]],[{"t":"Str","c":"random_patch_lat_lon"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/random_spool.html"]]],[{"t":"Str","c":"random_spool"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/ricker_moveout.html"]]],[{"t":"Str","c":"ricker_moveout"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/sin_wave_patch.html"]]],[{"t":"Str","c":"sin_wave_patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/spool_to_directory.html"]]],[{"t":"Str","c":"spool_to_directory"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/examples/wacky_dim_coord_patch.html"]]],[{"t":"Str","c":"wacky_dim_coord_patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-28"]]],[{"t":"Str","c":"exceptions"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/AttributeMergeError.html"]]],[{"t":"Str","c":"AttributeMergeError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/ChunkError.html"]]],[{"t":"Str","c":"ChunkError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/CoordDataError.html"]]],[{"t":"Str","c":"CoordDataError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/CoordError.html"]]],[{"t":"Str","c":"CoordError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/CoordMergeError.html"]]],[{"t":"Str","c":"CoordMergeError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/CoordSortError.html"]]],[{"t":"Str","c":"CoordSortError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/DASCoreError.html"]]],[{"t":"Str","c":"DASCoreError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/FilterValueError.html"]]],[{"t":"Str","c":"FilterValueError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/IncompatiblePatchError.html"]]],[{"t":"Str","c":"IncompatiblePatchError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/InvalidFiberFileError.html"]]],[{"t":"Str","c":"InvalidFiberFileError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/InvalidFiberIOError.html"]]],[{"t":"Str","c":"InvalidFiberIOError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/InvalidFileHandlerError.html"]]],[{"t":"Str","c":"InvalidFileHandlerError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/InvalidIndexVersionError.html"]]],[{"t":"Str","c":"InvalidIndexVersionError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/InvalidSpoolError.html"]]],[{"t":"Str","c":"InvalidSpoolError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/InvalidTimeRangeError.html"]]],[{"t":"Str","c":"InvalidTimeRangeError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/MissingOptionalDependencyError.html"]]],[{"t":"Str","c":"MissingOptionalDependencyError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/ParameterError.html"]]],[{"t":"Str","c":"ParameterError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/PatchAttributeError.html"]]],[{"t":"Str","c":"PatchAttributeError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/PatchConversionError.html"]]],[{"t":"Str","c":"PatchConversionError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/PatchDimError.html"]]],[{"t":"Str","c":"PatchDimError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/PatchError.html"]]],[{"t":"Str","c":"PatchError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/TimeError.html"]]],[{"t":"Str","c":"TimeError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/UnitError.html"]]],[{"t":"Str","c":"UnitError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/UnknownExampleError.html"]]],[{"t":"Str","c":"UnknownExampleError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/UnknownFiberFormatError.html"]]],[{"t":"Str","c":"UnknownFiberFormatError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/exceptions/UnsupportedKeywordError.html"]]],[{"t":"Str","c":"UnsupportedKeywordError"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-29"]]],[{"t":"Str","c":"io"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/PatchIO.html"]]],[{"t":"Str","c":"PatchIO"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-30"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/scan_to_df.html"]]],[{"t":"Str","c":"scan_to_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/write.html"]]],[{"t":"Str","c":"write"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-31"]]],[{"t":"Str","c":"FiberIO"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/FiberIO/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/FiberIO/get_supported_io_table.html"]]],[{"t":"Str","c":"get_supported_io_table"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/FiberIO/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/FiberIO/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/FiberIO/write.html"]]],[{"t":"Str","c":"write"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-32"]]],[{"t":"Str","c":"PatchFileSummary"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/PatchFileSummary/flat_dump.html"]]],[{"t":"Str","c":"flat_dump"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/core/PatchFileSummary/translate_d_to_step.html"]]],[{"t":"Str","c":"translate_d_to_step"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-33"]]],[{"t":"Str","c":"dasdae"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dasdae/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-34"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-35"]]],[{"t":"Str","c":"DASDAEV1"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dasdae/core/DASDAEV1/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dasdae/core/DASDAEV1/index.html"]]],[{"t":"Str","c":"index"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dasdae/core/DASDAEV1/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dasdae/core/DASDAEV1/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dasdae/core/DASDAEV1/write.html"]]],[{"t":"Str","c":"write"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-36"]]],[{"t":"Str","c":"dashdf5"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dashdf5/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-37"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dashdf5/core/ProdMLPatchAttrs.html"]]],[{"t":"Str","c":"ProdMLPatchAttrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-38"]]],[{"t":"Str","c":"DASHDF5"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dashdf5/core/DASHDF5/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dashdf5/core/DASHDF5/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/dashdf5/core/DASHDF5/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-39"]]],[{"t":"Str","c":"febus"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/febus/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-40"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/febus/core/Febus2.html"]]],[{"t":"Str","c":"Febus2"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/febus/core/FebusPatchAttrs.html"]]],[{"t":"Str","c":"FebusPatchAttrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-41"]]],[{"t":"Str","c":"Febus1"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/febus/core/Febus1/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/febus/core/Febus1/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/febus/core/Febus1/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-42"]]],[{"t":"Str","c":"h5simple"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/h5simple/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-43"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-44"]]],[{"t":"Str","c":"H5Simple"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/h5simple/core/H5Simple/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/h5simple/core/H5Simple/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/h5simple/core/H5Simple/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-45"]]],[{"t":"Str","c":"indexer"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-46"]]],[{"t":"Str","c":"AbstractIndexer"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/indexer/AbstractIndexer/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-47"]]],[{"t":"Str","c":"DirectoryIndexer"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/indexer/DirectoryIndexer/clear_cache.html"]]],[{"t":"Str","c":"clear_cache"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/indexer/DirectoryIndexer/get_contents.html"]]],[{"t":"Str","c":"get_contents"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/indexer/DirectoryIndexer/get_index_metadata.html"]]],[{"t":"Str","c":"get_index_metadata"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/indexer/DirectoryIndexer/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-48"]]],[{"t":"Str","c":"optodas"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/optodas/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-49"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/optodas/core/OptoDASPatchAttrs.html"]]],[{"t":"Str","c":"OptoDASPatchAttrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-50"]]],[{"t":"Str","c":"OptoDASV8"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/optodas/core/OptoDASV8/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/optodas/core/OptoDASV8/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/optodas/core/OptoDASV8/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-51"]]],[{"t":"Str","c":"pickle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-52"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-53"]]],[{"t":"Str","c":"PickleIO"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/pickle/core/PickleIO/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/pickle/core/PickleIO/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/pickle/core/PickleIO/write.html"]]],[{"t":"Str","c":"write"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-54"]]],[{"t":"Str","c":"prodml"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/prodml/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-55"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/prodml/core/ProdMLPatchAttrs.html"]]],[{"t":"Str","c":"ProdMLPatchAttrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/prodml/core/ProdMLV2_1.html"]]],[{"t":"Str","c":"ProdMLV2_1"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-56"]]],[{"t":"Str","c":"ProdMLV2_0"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/prodml/core/ProdMLV2_0/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/prodml/core/ProdMLV2_0/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/prodml/core/ProdMLV2_0/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-57"]]],[{"t":"Str","c":"rsf"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-58"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-59"]]],[{"t":"Str","c":"RSFV1"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/rsf/core/RSFV1/write.html"]]],[{"t":"Str","c":"write"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-60"]]],[{"t":"Str","c":"segy"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/segy/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-61"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-62"]]],[{"t":"Str","c":"SegyV2"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/segy/core/SegyV2/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/segy/core/SegyV2/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/segy/core/SegyV2/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-63"]]],[{"t":"Str","c":"sentek"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/sentek/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-64"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-65"]]],[{"t":"Str","c":"SentekV5"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/sentek/core/SentekV5/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/sentek/core/SentekV5/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/sentek/core/SentekV5/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-66"]]],[{"t":"Str","c":"tdms"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-67"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-68"]]],[{"t":"Str","c":"TDMSFormatterV4713"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/tdms/core/TDMSFormatterV4713/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/tdms/core/TDMSFormatterV4713/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/tdms/core/TDMSFormatterV4713/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-69"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/tdms/utils/parse_time_stamp.html"]]],[{"t":"Str","c":"parse_time_stamp"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/tdms/utils/type_not_supported.html"]]],[{"t":"Str","c":"type_not_supported"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-70"]]],[{"t":"Str","c":"terra15"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/terra15/utils.html"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-71"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/terra15/core/Terra15FormatterV5.html"]]],[{"t":"Str","c":"Terra15FormatterV5"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/terra15/core/Terra15FormatterV6.html"]]],[{"t":"Str","c":"Terra15FormatterV6"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-72"]]],[{"t":"Str","c":"Terra15FormatterV4"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/terra15/core/Terra15FormatterV4/get_format.html"]]],[{"t":"Str","c":"get_format"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/terra15/core/Terra15FormatterV4/read.html"]]],[{"t":"Str","c":"read"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/terra15/core/Terra15FormatterV4/scan.html"]]],[{"t":"Str","c":"scan"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-73"]]],[{"t":"Str","c":"wav"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-74"]]],[{"t":"Str","c":"core"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-75"]]],[{"t":"Str","c":"WavIO"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/io/wav/core/WavIO/write.html"]]],[{"t":"Str","c":"write"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-76"]]],[{"t":"Str","c":"proc"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-77"]]],[{"t":"Str","c":"aggregate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/aggregate/aggregate.html"]]],[{"t":"Str","c":"aggregate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-78"]]],[{"t":"Str","c":"basic"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/abs.html"]]],[{"t":"Str","c":"abs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/angle.html"]]],[{"t":"Str","c":"angle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/apply_operator.html"]]],[{"t":"Str","c":"apply_operator"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/dropna.html"]]],[{"t":"Str","c":"dropna"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/equals.html"]]],[{"t":"Str","c":"equals"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/imag.html"]]],[{"t":"Str","c":"imag"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/normalize.html"]]],[{"t":"Str","c":"normalize"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/pad.html"]]],[{"t":"Str","c":"pad"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/pipe.html"]]],[{"t":"Str","c":"pipe"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/real.html"]]],[{"t":"Str","c":"real"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/set_dims.html"]]],[{"t":"Str","c":"set_dims"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/squeeze.html"]]],[{"t":"Str","c":"squeeze"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/standardize.html"]]],[{"t":"Str","c":"standardize"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/transpose.html"]]],[{"t":"Str","c":"transpose"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/update.html"]]],[{"t":"Str","c":"update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/basic/update_attrs.html"]]],[{"t":"Str","c":"update_attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-79"]]],[{"t":"Str","c":"coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/assert_has_coords.html"]]],[{"t":"Str","c":"assert_has_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/coords_from_df.html"]]],[{"t":"Str","c":"coords_from_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/drop_coords.html"]]],[{"t":"Str","c":"drop_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/get_coord.html"]]],[{"t":"Str","c":"get_coord"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/rename_coords.html"]]],[{"t":"Str","c":"rename_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/snap_coords.html"]]],[{"t":"Str","c":"snap_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/sort_coords.html"]]],[{"t":"Str","c":"sort_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/coords/update_coords.html"]]],[{"t":"Str","c":"update_coords"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-80"]]],[{"t":"Str","c":"correlate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/correlate/correlate.html"]]],[{"t":"Str","c":"correlate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-81"]]],[{"t":"Str","c":"detrend"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/detrend/detrend.html"]]],[{"t":"Str","c":"detrend"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-82"]]],[{"t":"Str","c":"filter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/filter/gaussian_filter.html"]]],[{"t":"Str","c":"gaussian_filter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/filter/median_filter.html"]]],[{"t":"Str","c":"median_filter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/filter/pass_filter.html"]]],[{"t":"Str","c":"pass_filter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/filter/savgol_filter.html"]]],[{"t":"Str","c":"savgol_filter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/filter/sobel_filter.html"]]],[{"t":"Str","c":"sobel_filter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-83"]]],[{"t":"Str","c":"resample"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/resample/decimate.html"]]],[{"t":"Str","c":"decimate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/resample/interpolate.html"]]],[{"t":"Str","c":"interpolate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/resample/resample.html"]]],[{"t":"Str","c":"resample"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-84"]]],[{"t":"Str","c":"rolling"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/rolling/rolling.html"]]],[{"t":"Str","c":"rolling"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-85"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/select/select.html"]]],[{"t":"Str","c":"select"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-86"]]],[{"t":"Str","c":"taper"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/taper/taper.html"]]],[{"t":"Str","c":"taper"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-87"]]],[{"t":"Str","c":"units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/units/convert_units.html"]]],[{"t":"Str","c":"convert_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/units/set_units.html"]]],[{"t":"Str","c":"set_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/units/simplify_units.html"]]],[{"t":"Str","c":"simplify_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-88"]]],[{"t":"Str","c":"whiten"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/proc/whiten/whiten.html"]]],[{"t":"Str","c":"whiten"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-89"]]],[{"t":"Str","c":"transform"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-90"]]],[{"t":"Str","c":"differentiate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/differentiate/differentiate.html"]]],[{"t":"Str","c":"differentiate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-91"]]],[{"t":"Str","c":"dispersion"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/dispersion/dispersion_phase_shift.html"]]],[{"t":"Str","c":"dispersion_phase_shift"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-92"]]],[{"t":"Str","c":"fft"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/fft/rfft.html"]]],[{"t":"Str","c":"rfft"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-93"]]],[{"t":"Str","c":"fourier"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/fourier/dft.html"]]],[{"t":"Str","c":"dft"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/fourier/idft.html"]]],[{"t":"Str","c":"idft"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-94"]]],[{"t":"Str","c":"integrate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/integrate/integrate.html"]]],[{"t":"Str","c":"integrate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-95"]]],[{"t":"Str","c":"spectro"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/spectro/spectrogram.html"]]],[{"t":"Str","c":"spectrogram"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-96"]]],[{"t":"Str","c":"strain"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/transform/strain/velocity_to_strain_rate.html"]]],[{"t":"Str","c":"velocity_to_strain_rate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-97"]]],[{"t":"Str","c":"units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/assert_dtype_compatible_with_units.html"]]],[{"t":"Str","c":"assert_dtype_compatible_with_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/convert_units.html"]]],[{"t":"Str","c":"convert_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/get_factor_and_unit.html"]]],[{"t":"Str","c":"get_factor_and_unit"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/get_filter_units.html"]]],[{"t":"Str","c":"get_filter_units"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/get_quantity.html"]]],[{"t":"Str","c":"get_quantity"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/get_quantity_str.html"]]],[{"t":"Str","c":"get_quantity_str"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/get_registry.html"]]],[{"t":"Str","c":"get_registry"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/get_unit.html"]]],[{"t":"Str","c":"get_unit"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/units/invert_quantity.html"]]],[{"t":"Str","c":"invert_quantity"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-98"]]],[{"t":"Str","c":"utils"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/plotting.html"]]],[{"t":"Str","c":"plotting"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-99"]]],[{"t":"Str","c":"chunk"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/chunk/get_intervals.html"]]],[{"t":"Str","c":"get_intervals"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-100"]]],[{"t":"Str","c":"ChunkManager"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/chunk/ChunkManager/chunk.html"]]],[{"t":"Str","c":"chunk"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/chunk/ChunkManager/get_instruction_df.html"]]],[{"t":"Str","c":"get_instruction_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-101"]]],[{"t":"Str","c":"display"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/display/array_to_text.html"]]],[{"t":"Str","c":"array_to_text"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/display/attrs_to_text.html"]]],[{"t":"Str","c":"attrs_to_text"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/display/get_dascore_text.html"]]],[{"t":"Str","c":"get_dascore_text"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/display/get_nice_text.html"]]],[{"t":"Str","c":"get_nice_text"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-102"]]],[{"t":"Str","c":"docs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/docs/compose_docstring.html"]]],[{"t":"Str","c":"compose_docstring"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/docs/format_dtypes.html"]]],[{"t":"Str","c":"format_dtypes"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/docs/objs_to_doc_df.html"]]],[{"t":"Str","c":"objs_to_doc_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-103"]]],[{"t":"Str","c":"downloader"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/downloader/fetch.html"]]],[{"t":"Str","c":"fetch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/downloader/get_registry_df.html"]]],[{"t":"Str","c":"get_registry_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-104"]]],[{"t":"Str","c":"hdf5"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/open_hdf5_file.html"]]],[{"t":"Str","c":"open_hdf5_file"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/unpack_scalar_h5_dataset.html"]]],[{"t":"Str","c":"unpack_scalar_h5_dataset"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-105"]]],[{"t":"Str","c":"H5Reader"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/H5Reader/get_handle.html"]]],[{"t":"Str","c":"get_handle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-106"]]],[{"t":"Str","c":"H5Writer"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/H5Writer/get_handle.html"]]],[{"t":"Str","c":"get_handle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-107"]]],[{"t":"Str","c":"HDF5Reader"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/HDF5Reader/get_handle.html"]]],[{"t":"Str","c":"get_handle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-108"]]],[{"t":"Str","c":"HDF5Writer"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/HDF5Writer/get_handle.html"]]],[{"t":"Str","c":"get_handle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-109"]]],[{"t":"Str","c":"HDFPatchIndexManager"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/HDFPatchIndexManager/decode_table.html"]]],[{"t":"Str","c":"decode_table"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/HDFPatchIndexManager/encode_table.html"]]],[{"t":"Str","c":"encode_table"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/HDFPatchIndexManager/get_index.html"]]],[{"t":"Str","c":"get_index"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/HDFPatchIndexManager/validate_version.html"]]],[{"t":"Str","c":"validate_version"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/hdf5/HDFPatchIndexManager/write_update.html"]]],[{"t":"Str","c":"write_update"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-110"]]],[{"t":"Str","c":"io"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/get_handle_from_resource.html"]]],[{"t":"Str","c":"get_handle_from_resource"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/obspy_to_patch.html"]]],[{"t":"Str","c":"obspy_to_patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/patch_to_obspy.html"]]],[{"t":"Str","c":"patch_to_obspy"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/patch_to_xarray.html"]]],[{"t":"Str","c":"patch_to_xarray"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/xarray_to_patch.html"]]],[{"t":"Str","c":"xarray_to_patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-111"]]],[{"t":"Str","c":"BinaryReader"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/BinaryReader/get_handle.html"]]],[{"t":"Str","c":"get_handle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-112"]]],[{"t":"Str","c":"BinaryWriter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/BinaryWriter/get_handle.html"]]],[{"t":"Str","c":"get_handle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-113"]]],[{"t":"Str","c":"IOResourceManager"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/IOResourceManager/close_all.html"]]],[{"t":"Str","c":"close_all"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/io/IOResourceManager/get_resource.html"]]],[{"t":"Str","c":"get_resource"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-114"]]],[{"t":"Str","c":"mapping"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-115"]]],[{"t":"Str","c":"FrozenDict"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/mapping/FrozenDict/copy.html"]]],[{"t":"Str","c":"copy"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-116"]]],[{"t":"Str","c":"misc"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/CacheDescriptor.html"]]],[{"t":"Str","c":"CacheDescriptor"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/MethodNameSpace.html"]]],[{"t":"Str","c":"MethodNameSpace"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/all_close.html"]]],[{"t":"Str","c":"all_close"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/all_diffs_close_enough.html"]]],[{"t":"Str","c":"all_diffs_close_enough"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/broadcast_for_index.html"]]],[{"t":"Str","c":"broadcast_for_index"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/cached_method.html"]]],[{"t":"Str","c":"cached_method"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/check_filter_kwargs.html"]]],[{"t":"Str","c":"check_filter_kwargs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/check_filter_range.html"]]],[{"t":"Str","c":"check_filter_range"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/get_middle_value.html"]]],[{"t":"Str","c":"get_middle_value"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/get_parent_code_name.html"]]],[{"t":"Str","c":"get_parent_code_name"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/get_stencil_coefs.html"]]],[{"t":"Str","c":"get_stencil_coefs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/is_valid_coord_str.html"]]],[{"t":"Str","c":"is_valid_coord_str"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/iter_files.html"]]],[{"t":"Str","c":"iter_files"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/iterate.html"]]],[{"t":"Str","c":"iterate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/maybe_get_attrs.html"]]],[{"t":"Str","c":"maybe_get_attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/maybe_get_items.html"]]],[{"t":"Str","c":"maybe_get_items"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/optional_import.html"]]],[{"t":"Str","c":"optional_import"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/register_func.html"]]],[{"t":"Str","c":"register_func"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/sanitize_range_param.html"]]],[{"t":"Str","c":"sanitize_range_param"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/separate_coord_info.html"]]],[{"t":"Str","c":"separate_coord_info"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/suppress_warnings.html"]]],[{"t":"Str","c":"suppress_warnings"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/to_str.html"]]],[{"t":"Str","c":"to_str"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/unbyte.html"]]],[{"t":"Str","c":"unbyte"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/misc/warn_or_raise.html"]]],[{"t":"Str","c":"warn_or_raise"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-117"]]],[{"t":"Str","c":"models"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/models/sensible_model_equals.html"]]],[{"t":"Str","c":"sensible_model_equals"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-118"]]],[{"t":"Str","c":"DascoreBaseModel"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/models/DascoreBaseModel/get_summary_df.html"]]],[{"t":"Str","c":"get_summary_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/models/DascoreBaseModel/new.html"]]],[{"t":"Str","c":"new"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-119"]]],[{"t":"Str","c":"patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/check_patch_attrs.html"]]],[{"t":"Str","c":"check_patch_attrs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/check_patch_dims.html"]]],[{"t":"Str","c":"check_patch_dims"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/get_default_patch_name.html"]]],[{"t":"Str","c":"get_default_patch_name"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/get_dim_sampling_rate.html"]]],[{"t":"Str","c":"get_dim_sampling_rate"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/get_dim_value_from_kwargs.html"]]],[{"t":"Str","c":"get_dim_value_from_kwargs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/get_multiple_dim_value_from_kwargs.html"]]],[{"t":"Str","c":"get_multiple_dim_value_from_kwargs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/get_start_stop_step.html"]]],[{"t":"Str","c":"get_start_stop_step"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/merge_patches.html"]]],[{"t":"Str","c":"merge_patches"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/patch_function.html"]]],[{"t":"Str","c":"patch_function"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/patches_to_df.html"]]],[{"t":"Str","c":"patches_to_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/patch/scan_patches.html"]]],[{"t":"Str","c":"scan_patches"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-120"]]],[{"t":"Str","c":"pd"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/adjust_segments.html"]]],[{"t":"Str","c":"adjust_segments"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/dataframe_to_patch.html"]]],[{"t":"Str","c":"dataframe_to_patch"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/fill_defaults_from_pydantic.html"]]],[{"t":"Str","c":"fill_defaults_from_pydantic"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/filter_df.html"]]],[{"t":"Str","c":"filter_df"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/get_column_names_from_dim.html"]]],[{"t":"Str","c":"get_column_names_from_dim"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/get_dim_names_from_columns.html"]]],[{"t":"Str","c":"get_dim_names_from_columns"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/get_interval_columns.html"]]],[{"t":"Str","c":"get_interval_columns"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/get_regex.html"]]],[{"t":"Str","c":"get_regex"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/list_ser_to_str.html"]]],[{"t":"Str","c":"list_ser_to_str"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/patch_to_dataframe.html"]]],[{"t":"Str","c":"patch_to_dataframe"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/split_df_query.html"]]],[{"t":"Str","c":"split_df_query"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/pd/yield_range_tuple_from_kwargs.html"]]],[{"t":"Str","c":"yield_range_tuple_from_kwargs"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-121"]]],[{"t":"Str","c":"progress"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/progress/get_progress_instance.html"]]],[{"t":"Str","c":"get_progress_instance"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/progress/track.html"]]],[{"t":"Str","c":"track"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-122"]]],[{"t":"Str","c":"time"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/dtype_time_like.html"]]],[{"t":"Str","c":"dtype_time_like"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/get_max_min_times.html"]]],[{"t":"Str","c":"get_max_min_times"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/is_datetime64.html"]]],[{"t":"Str","c":"is_datetime64"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/is_timedelta64.html"]]],[{"t":"Str","c":"is_timedelta64"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/to_datetime64.html"]]],[{"t":"Str","c":"to_datetime64"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/to_float.html"]]],[{"t":"Str","c":"to_float"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/to_int.html"]]],[{"t":"Str","c":"to_int"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/time/to_timedelta64.html"]]],[{"t":"Str","c":"to_timedelta64"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-123"]]],[{"t":"Str","c":"transformatter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/transformatter/FourierTransformatter.html"]]],[{"t":"Str","c":"FourierTransformatter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-124"]]],[{"t":"Str","c":"BaseTransformatter"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/utils/transformatter/BaseTransformatter/rename_dims.html"]]],[{"t":"Str","c":"rename_dims"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-125"]]],[{"t":"Str","c":"viz"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/viz/VizPatchNameSpace.html"]]],[{"t":"Str","c":"VizPatchNameSpace"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-126"]]],[{"t":"Str","c":"spectrogram"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/viz/spectrogram/spectrogram.html"]]],[{"t":"Str","c":"spectrogram"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-127"]]],[{"t":"Str","c":"waterfall"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/viz/waterfall/waterfall.html"]]],[{"t":"Str","c":"waterfall"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:quarto-sidebar-section-128"]]],[{"t":"Str","c":"wiggle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-sidebar:/api/dascore/viz/wiggle/wiggle.html"]]],[{"t":"Str","c":"wiggle"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Introduction"]]],[{"t":"Str","c":"Introduction"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:/index.html"]]],[{"t":"Str","c":"/index.html"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Tutorial"]]],[{"t":"Str","c":"Tutorial"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:/tutorial/concepts.html"]]],[{"t":"Str","c":"/tutorial/concepts.html"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Recipes"]]],[{"t":"Str","c":"Recipes"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:/recipes/overview.html"]]],[{"t":"Str","c":"/recipes/overview.html"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Notes"]]],[{"t":"Str","c":"Notes"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:/notes/notes.html"]]],[{"t":"Str","c":"/notes/notes.html"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:Contributing"]]],[{"t":"Str","c":"Contributing"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:/contributing/contributing.html"]]],[{"t":"Str","c":"/contributing/contributing.html"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:API"]]],[{"t":"Str","c":"API"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:/api/dascore.html"]]],[{"t":"Str","c":"/api/dascore.html"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-int-navbar:https://github.com/dasdae/dascore"]]],[{"t":"Str","c":"https://github.com/dasdae/dascore"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-breadcrumbs-ca7f8cd10a5c633b30bcc55700c7d9a8"]]],[{"t":"Str","c":"dascore"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-breadcrumbs-d825c53d89d9cacb3e034bcb138f3d37"]]],[{"t":"Str","c":"transform"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-breadcrumbs-4e4aaf3fee165531347b772f66b4f30c"]]],[{"t":"Str","c":"integrate"}]]}]}]]},{"t":"Div","c":[["quarto-meta-markdown",["hidden"],[]],[{"t":"Para","c":[{"t":"Span","c":[["",["hidden"],[["render-id","quarto-twittercardtitle"]]],[]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-ogcardtitle"]]],[]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-metasitename"]]],[{"t":"Str","c":"DASCore"},{"t":"Space"},{"t":"Str","c":"(0.1.1.dev2)"}]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-twittercarddesc"]]],[]]},{"t":"SoftBreak"},{"t":"Span","c":[["",["hidden"],[["render-id","quarto-ogcardddesc"]]],[]]}]}]]}]}
diff --git a/docs/index.css b/docs/index.css
deleted file mode 100644
index a1ae93c41..000000000
--- a/docs/index.css
+++ /dev/null
@@ -1,233 +0,0 @@
-
-.content-block {
- padding-top: 20px;
- padding-bottom: 10px;
- margin-left: 30px;
- margin-right: 30px;
- }
-
-
- @media(min-width: 900px) {
- .content-block {
- margin-left: 50px;
- margin-right: 50px;
- }
- }
-
- @media (min-width: 1200px) {
- .content-block {
- max-width: 1100px;
- margin-left: auto;
- margin-right: auto;
- }
- }
-
- .hero-banner {
- position: relative;
- background-color: rgb(237,243,249);
- display: flex;
- justify-content: center;
- }
-
- .hero-banner h1 {
- color: #39729E;
- font-size: 2.5rem;
- }
-
-
- .hero-banner .hero-image {
- position: absolute;
- display: none;
- height: auto;
- }
-
- @media (min-width: 1000px) {
- .hero-banner .hero-image {
- display: initial;
- width: 270px;
- }
- }
-
- @media (min-width: 1200px) {
- .hero-banner .hero-image {
- width: 340px;
- }
- }
-
- @media (min-width: 1400px) {
- .hero-banner .hero-image {
- width: 440px;
- }
- }
-
- .hero-banner .hero-image p {
- margin-bottom: 0;
- }
-
- .hero-banner .hero-image-left {
- left: 0;
- bottom: 0;
- }
-
- .hero-banner .hero-image-right {
- right: 0;
- bottom: 0;
- }
-
-
- .hero-banner .content-block {
- max-width: 600px;
- z-index: 2;
- }
-
- .hero-banner a {
- text-decoration: none;
- }
-
- .hero-banner h3 {
- margin-top: 1.3rem;
- margin-bottom: 1.3rem;
- }
-
- .hero-banner h4 {
- margin-top: 0;
- }
-
- .hero-banner a[role="button"] {
- margin-right: 17px;
- margin-top: 0.6rem;
- margin-bottom: 1.6rem;
- }
-
-
- .hero-banner #btn-guide {
- background-color: #959595 !important;
- border: none;
- }
-
-
-
-
- .hero-banner ul {
- padding-inline-start: 21px;
- font-size: 1.1rem;
- }
-
- .hero-banner ul li {
- padding-bottom: 0.4rem;
- }
-
-
- .alt-background {
- background-color: rgb(247,249,251);
- border-top: 1px solid #dee2e6;
- border-bottom: 1px solid #dee2e6;
- }
-
- .hello-quarto {
- padding-bottom: 1rem;
- }
-
- @media (min-width: 600px) {
- .hello-quarto-banner {
- display: inline-flex;
- align-content: center;
- justify-content: center;
- }
-
- .hello-quarto-banner h1 {
- margin-right: 40px;
- }
-
- .hello-quarto-banner ul {
-
- }
- }
-
- .hello-quarto-banner .nav-pills .nav-link.active, .nav-pills .show>.nav-link {
- border: none;
- border-bottom: 2px solid #39729E !important;
- color: #39729E;
- background-color: transparent;
-
- }
-
- .hello-quarto-banner .nav-pills button {
- width: 125px;
- }
-
- .hello-quarto .tab-content {
- border: none;
- padding: 0;
- color: rgb(84, 85, 85);
- }
-
- .hello-quarto .tab-content p {
- font-size: 1.1em;
- margin-bottom: 1.5em;
- }
-
- .hello-quarto div.sourceCode {
- background-color: white;
- border: 1px solid #dee2e6;
- }
-
- .hello-output {
- background-color: white;
- border: 1px solid #dee2e6;
- max-height: 660px;
- }
-
- .features {
- padding-bottom: 2em;
- }
-
- .feature {
- margin-top: 20px;
- }
-
- @media (min-width: 800px) {
- .features {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- margin: 0 0 0 -30px;
- width: calc(100% + 30px);
- }
- .feature {
- width: calc(33% - 30px);
- margin: 20px 0 0 30px;
- }
- }
-
- .feature h3 {
- margin-top: 0;
- }
-
- .feature p:first-of-type {
- margin-bottom: 0.2rem;
- color: rgb(84, 85, 85);
- }
-
- .get-started {
- text-align: center;
- padding-bottom: 2rem;
- }
-
- .get-started h3 {
- margin-top: 1rem;
- margin-bottom: 2rem;
- }
-
- nav.page-navigation {
- display: none;
- }
-
- .nav-footer {
- border-top: none !important;
- }
-
- .nav-pills .nav-link.active, .nav-pills .show>.nav-link {
- color: #fff;
- background-color: #ff7518;
- }
diff --git a/docs/notes/notes.qmd b/docs/notes/notes.qmd
deleted file mode 100644
index 0c60a8997..000000000
--- a/docs/notes/notes.qmd
+++ /dev/null
@@ -1,14 +0,0 @@
----
-title: Notes
----
-
-This section provides understanding-oriented explanations of DASCore implementation and design decisions.
-
-- [Coordinate Internals](coordinate_internals.qmd) explains coordinate families, segmented coordinates, summary envelopes, and exact scan payloads.
-- [PatchAttrs](patch_attrs.qmd) explains non-coordinate metadata boundaries and `data_type` policy.
-- [Documentation Strategy](doc_strategy.qmd)
-- [Fourier Transforms](dft_notes.qmd)
-- [Velocity to Strain Rate](velocity_to_strain_rate.qmd)
-- [Spool Index](spool_index.qmd)
-- [Spool Selection](spool_selection.qmd)
-- [Spool Chunking](spool_chunking.qmd)
diff --git a/docs/recipes/contributing_to_documentation.qmd b/docs/recipes/contributing_to_documentation.qmd
deleted file mode 100644
index 034d45784..000000000
--- a/docs/recipes/contributing_to_documentation.qmd
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: Contributing to Documentation
----
-
-Here, we elaborate on how you can build new documentation or make changes to existing DASCore documentation.
-
-## Building/Editing documentation
-
-First, install the [Quarto](https://quarto.org/docs/get-started/)
-
-To ensure Quarto is installed properly and to get the installation version:
-
-```bash
-quarto --version
-```
-
-If you have not already installed jupyter in your environment, install it using conda:
-
-```bash
-conda install jupyter
-```
-
-Go to the DASCore directory on your machine:
-
-```bash
-cd dascore
-```
-
-Then generate DASCore's API markdown files by running the following script:
-
-```bash
-python scripts/build_api_docs.py
-```
-
-```bash
-quarto preview docs
-```
-
-:::{.note}
-This will take a few minutes the first time you run it. After that, the results are cached and only the changed files are re-rendered.
-:::
-
-Now, you can make new documentation or make changes to the "index.qmd" file on the /dascore/docs/ directory and so on.
-However, if you make changes to any of DASCore's docstring, you need to re-run the build_api_docs.py script for the changes to appear.
diff --git a/docs/recipes/overview.qmd b/docs/recipes/overview.qmd
deleted file mode 100644
index e43a6f0f4..000000000
--- a/docs/recipes/overview.qmd
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Overview"
----
-
-Welcome to DASCore's cookbook! This is a collection of examples (recipes) to demonstrate how to perform various tasks with DASCore. Use the collapsible menu in the sidebar to browse the recipes and consider [contributing](../contributing/contributing.qmd) some examples of your own.
diff --git a/docs/styles.css b/docs/styles.css
deleted file mode 100644
index a26f8cde8..000000000
--- a/docs/styles.css
+++ /dev/null
@@ -1,78 +0,0 @@
-/* css styles */
-
-body {
- font-size: 18px;
-}
-
-table {
- border-collapse: collapse;
- border: 2px solid rgb(200, 200, 200);
- letter-spacing: 1px;
- font-size: 0.8rem;
- display: table;
- width: 100%;
- padding-bottom: 4px;
-}
-
-caption {
- padding: 10px;
- caption-side: top;
- color: #666;
- text-align: center;
- letter-spacing: 1px;
- font-size: small;
-}
-
-td {
- border: 0.5px solid rgb(190, 190, 190);
- padding: 10px 20px;
- text-align: left;
- vertical-align: middle;
- /*white-space: pre;*/
-}
-
-/*Try not to let first column wrap.*/
-td:first-child {
- white-space: nowrap;
-}
-
-th {
- border: 0.5px solid rgb(190, 190, 190);
- padding: 10px 20px;
- text-align: center;
-}
-
-
-/*This makes every other row striped. */
-tbody tr:nth-child(odd) {
- background-color: rgba(210, 209, 209, 0.35);
-}
-
-
-/* Custom classes */
-
-.padded_bottom_10pt {
- padding-bottom: 10pt;
-}
-
-.def_block {
- background-color: rgba(210, 209, 209, 0.3);
- margin: 1em;
- box-sizing: content-box;
- padding-left: 10px;
- padding-right: 10px;
- border-radius: 25px;
- padding-bottom: 0px;
- padding-top: 2px;
-}
-
-.origin_table {
- border: 0em;
- width: 100%;
- margin: 0;
- font-size: 1em;
- border-style: hidden !important;
- font-family: 'Bree Serif', serif;
- display: block;
- margin-bottom: 10px;
-}
diff --git a/great-docs.yml b/great-docs.yml
new file mode 100644
index 000000000..08d2e3bac
--- /dev/null
+++ b/great-docs.yml
@@ -0,0 +1,455 @@
+# Great Docs Configuration for DASCore
+# See https://posit-dev.github.io/great-docs/user-guide/configuration.html
+#
+# `great-docs build` renders the whole site: the narrative sections below,
+# the API reference generated from docstrings, and the agent-facing outputs
+# (llms.txt, per-page markdown, .well-known/agent-skills).
+
+display_name: DASCore
+
+# Docstring format used throughout the package.
+parser: numpy
+
+# Runtime introspection (needed so @compose_docstring-templated docstrings
+# render with their substitutions filled in).
+dynamic: true
+
+site_url: "https://dascore.org"
+repo: "https://github.com/DASDAE/dascore"
+
+logo:
+ light: assets/logo.png
+ dark: assets/logo.png
+ alt: "DASCore logo"
+ show_title: true
+
+# Pre-render hooks:
+# - greatdocs_build_fixups.py patches generated index.qmd/changelog.qmd
+# (undo code-comment heading bump, drop duplicate changelog version headings).
+# - greatdocs_alias_inventory.py adds alias entries (Patch methods, re-export
+# paths, module links) to the interlink inventory so alias-style cross
+# references keep resolving.
+pre_render:
+ - scripts/greatdocs_build_fixups.py
+ - scripts/greatdocs_alias_inventory.py
+
+# Site styling. `yeti` is the Bootswatch theme the previous doc build used, so
+# fonts, colors and spacing carry over unchanged; great-docs.scss is listed
+# explicitly (great-docs appends it otherwise) so assets/dascore.scss can layer
+# on top of it. That file carries the old site's custom CSS.
+site:
+ theme:
+ - yeti
+ - great-docs.scss
+ - assets/dascore.scss
+
+# yeti's $primary, so great-docs' accent matches the theme's link color.
+accent_color:
+ light: "#008cba"
+ dark: "#5bc0de"
+
+# Light navbar, as on the old site (text color is auto-picked for contrast).
+navbar_color:
+ light: "#eeeeee"
+ dark: "#222222"
+
+# Bibliography so citations (e.g. @lindsey2020broadband) in docstrings and
+# narrative pages resolve instead of hanging pandoc.
+bibliography: docs/references.bib
+
+authors:
+ - name: The DASDAE contributors
+ role: Author
+ github: DASDAE
+
+# Narrative documentation. Each section becomes a top-level site directory
+# matching the old site layout, so existing relative links keep working.
+sections:
+ - title: Tutorial
+ dir: tutorial
+ - title: Recipes
+ dir: recipes
+ index: true
+ - title: Notes
+ dir: notes
+ index: true
+ - title: Contributing
+ dir: contributing
+ index: true
+ - title: About
+ dir: about
+ index: true
+ # One page for the whole utils package: a page per helper would triple the
+ # site for objects that are supporting cast rather than the main interface.
+ - title: Utilities
+ dir: utilities
+
+# Names picked up by discovery that are implementation details or
+# accidental re-exports (dascore modules do not define __all__).
+exclude:
+ - warnings
+ - compat.MATERIALIZE_NOW
+ - compat.random_state
+ - examples.fetch
+ - examples.iterate
+ - examples.get_patch_names
+ - examples.random_state
+ - examples.register_func
+ - examples.EXAMPLE_PATCHES
+ - examples.EXAMPLE_SPOOLS
+ - examples.UnknownExampleError
+ - io.PatchNameSpace
+ - proc.ArrayLike
+ - proc.PatchType
+ - proc.apply_operator
+ - proc.array
+ - proc.compose_docstring
+ - proc.get_parent_code_name
+ - proc.get_dim_axis_value
+ - proc.iterate
+ - proc.select_values_description
+ - proc.CoordError
+ - proc.ParameterError
+ - proc.PatchCoordinateError
+ - proc.PatchError
+ - units.assert_dtype_compatible_with_units
+ - units.dtype_time_like
+ - units.is_array
+ - units.is_datetime64
+ - units.is_timedelta64
+ - units.iterate
+ - units.maybe_convert_percent_to_fraction
+ - units.quant_sequence_to_quant_array
+ - units.unbyte
+ - units.UnitError
+ - viz.PatchNameSpace
+ - viz.VizPatchNameSpace
+
+# Curated API reference. Grouped by task, mirroring how the tutorial
+# introduces the library.
+reference:
+ - title: Getting Data In and Out
+ desc: Read, scan, and write fiber-optic sensing data files
+ contents:
+ - spool
+ - read
+ - scan
+ - scan_to_df
+ - scan_payloads
+ - write
+ - get_format
+
+ - title: Core Classes
+ desc: The central data structures of DASCore
+ contents:
+ - name: Patch
+ members: [data, coords, dims, attrs, shape, size, ndim, dtype, seconds, channel_count, coord_shapes, flat_dump, get_coord, get_patch_name, get_registered_namespaces, summary, T]
+ - name: BaseSpool
+ members: [chunk, concatenate, get_contents, get_patch_names, get_registered_namespaces, map, select, sort, split, stack, unselect, update]
+ - name: Spool
+ members: [from_directory, from_file, attach_inventory, chunk_plan, conform_to_inventory, enrich, remove_inventory, split_by, spool_path, indexer, has_live_patches]
+ - name: PatchAttrs
+ members: [drop, drop_private, flat_dump, from_dict, get, get_summary_df, items, new, update]
+ - name: PatchSummary
+ members: [dim_tuple, dump_structured, flat_dump, from_patch, get_coord_summary, get_summary_df, new, summary]
+ - name: CoordManager
+ members: [convert_units, coord_range, coord_shapes, coord_size, decimate, dim_to_coord_map, disassociate_coord, drop_coords, drop_disassociated_coords, drop_private_coords, equals, flip, get_array, get_axis, get_coord, get_coord_tuple_map, get_summary_df, keys, make_broadcastable_to, max, min, ndim, new, order, rename_coord, select, set_dims, set_units, shape, simplify_units, size, snap, sort, squeeze, step, to_summary_dict, transpose, update, validate_data]
+ - name: proc.BaseCoord
+ members: [align_to, approx_equal, change_length, convert_units, coord_range, data, degenerate, empty, evenly_sampled, fingerprint, get_discontinuities, get_next_index, get_sample_count, get_slice_tuple, get_summary_df, index, limits, max, min, ndim, new, order, reduce_coord, reverse_sorted, select, set_units, simplify, simplify_units, size, snap, sort, sorted, to_summary, unit_str, update, update_data, update_limits]
+ - name: proc.CoordSegmented
+ members: [from_array, get_discontinuities, segment_count, simplify, values]
+ - core.coords.CoordRange
+ - core.coords.CoordMonotonicArray
+ - core.coords.CoordArray
+ - core.coords.CoordPartial
+ - core.coords.CoordString
+ - core.coords.CoordSummary
+ - core.coords.concat_coords
+ - get_coord
+ - get_coord_manager
+
+ - title: Patch Processing
+ desc: >
+ Processing routines. Each of these functions is also attached to
+ Patch as a method (e.g. patch.pass_filter(...)).
+ contents:
+ - proc.abs
+ - proc.add_distance_to
+ - proc.aggregate.aggregate
+ - proc.aggregate.all
+ - proc.aggregate.any
+ - proc.aggregate.first
+ - proc.aggregate.last
+ - proc.aggregate.max
+ - proc.aggregate.mean
+ - proc.aggregate.median
+ - proc.aggregate.min
+ - proc.aggregate.std
+ - proc.aggregate.sum
+ - proc.align_patch_coords
+ - proc.align_to_coord
+ - proc.angle
+ - proc.append_dims
+ - proc.bool_patch
+ - proc.conj
+ - proc.coords_from_df
+ - proc.correlate
+ - proc.convert_units
+ - proc.correlate_shift
+ - proc.decimate
+ - proc.demean
+ - proc.detrend
+ - proc.demedian
+ - proc.drop_coords
+ - proc.drop_private_coords
+ - proc.dropna
+ - proc.enrich
+ - proc.equals
+ - proc.fillna
+ - proc.flip
+ - proc.full
+ - proc.gaussian_filter
+ - proc.get_array
+ - proc.get_axis
+ - proc.hampel_filter
+ - proc.imag
+ - proc.interpolate
+ - proc.line_mute
+ - proc.make_broadcastable_to
+ - proc.median_filter
+ - proc.normalize
+ - proc.notch_filter
+ - proc.order
+ - proc.pad
+ - proc.pass_filter
+ - proc.pipe
+ - proc.real
+ - proc.rename_coords
+ - proc.resample
+ - proc.rolling
+ - proc.roll
+ - proc.savgol_filter
+ - proc.select
+ - proc.set_dims
+ - proc.set_units
+ - proc.simplify_units
+ - proc.slope_filter
+ - proc.slope_mute
+ - proc.snap_coords
+ - proc.sobel_filter
+ - proc.sort_coords
+ - proc.split_gaps
+ - proc.squeeze
+ - proc.standardize
+ - proc.taper
+ - proc.taper_range
+ - proc.transpose
+ - proc.unselect
+ - proc.update
+ - proc.update_attrs
+ - proc.update_coords
+ - proc.where
+ - proc.whiten
+ - proc.wiener_filter
+ - patch_function
+
+ - title: Transforms
+ desc: Transformations that change patch units or domain
+ contents:
+ - transform.dft
+ - transform.idft
+ - transform.stft
+ - transform.istft
+ - transform.envelope
+ - transform.differentiate
+ - transform.fbe
+ - transform.hilbert
+ - transform.integrate
+ - transform.kurtosis
+ - transform.stalta
+ - transform.dispersion_phase_shift
+ - transform.phase_weighted_stack
+ - transform.radians_to_strain
+ - transform.tau_p
+ - transform.velocity_to_strain_rate
+ - transform.velocity_to_strain_rate_edgeless
+
+ - title: Visualization
+ desc: Matplotlib-based plotting of patches
+ contents:
+ - viz.waterfall
+ - viz.wiggle
+ - viz.spectrogram
+ - viz.specplot
+ - viz.map_fiber
+
+ - title: Example Data
+ desc: Functions for generating example patches and spools
+ contents:
+ - get_example_patch
+ - get_example_spool
+ - examples.chirp
+ - examples.deformation_rate_event_1
+ - examples.delta_patch
+ - examples.dispersion_event
+ - examples.diverse_spool
+ - examples.example_event_1
+ - examples.example_event_2
+ - examples.febus_dss_mine_1
+ - examples.febus_dss_mine_2
+ - examples.forge_dss
+ - examples.forge_dts
+ - examples.inventory_patch_pair
+ - examples.nd_patch
+ - examples.patch_with_null
+ - examples.random_directory_spool
+ - examples.random_patch
+ - examples.random_patch_lat_lon
+ - examples.random_patch_xyz
+ - examples.random_spool
+ - examples.ricker_moveout
+ - examples.sin_wave_patch
+ - examples.spool_to_directory
+ - examples.wacky_dim_coord_patch
+
+ - title: Units and Time
+ desc: Unit and time conversion utilities
+ contents:
+ - get_quantity
+ - get_unit
+ - to_datetime64
+ - to_timedelta64
+ - to_float
+ - units.convert_units
+ - units.get_byte_count
+ - units.get_factor_and_unit
+ - units.get_filter_units
+ - units.get_inverted_quant
+ - units.get_quantity_str
+ - units.get_registry
+ - units.invert_quantity
+ - units.is_data_size
+ - units.is_percent
+
+ - title: IO Interfaces
+ desc: Interoperability with other libraries and the FiberIO plugin system
+ contents:
+ - io.FiberIO
+ - io.PatchIO
+ - io.ScanPayload
+ - io.make_scan_payload
+ - io.dataframe_to_patch
+ - io.patch_to_dataframe
+ - io.obspy_to_patch
+ - io.patch_to_obspy
+ - io.xarray_to_patch
+ - io.patch_to_xarray
+ - io.BinaryReader
+ - io.BinaryWriter
+ # Restrict to the public method: documenting the `constructor` class
+ # attribute (h5py.File) makes griffe's dynamic introspection fail while
+ # resolving h5py internals, which would drop the whole API reference to
+ # static analysis (mis-framing module-shadowing functions like
+ # proc.correlate as modules).
+ - name: io.H5Reader
+ members: [get_handle]
+ - name: io.H5Writer
+ members: [get_handle]
+
+ - title: Configuration
+ desc: Reading and setting DASCore's runtime configuration
+ contents:
+ - config.get_config
+ - config.set_config
+ - config.config_context
+ - config.config_attr
+ - config.reset_config
+ - config.DascoreConfig
+
+ - title: Inventory
+ desc: >
+ The DASDAE inventory model, describing the cables, interrogators and
+ channels a dataset was recorded with.
+ contents:
+ - Inventory
+ - inventory
+ - core.inventory.Network
+ - core.inventory.Station
+ - core.inventory.Channel
+ - core.inventory.Cable
+ - core.inventory.FiberArray
+ - core.inventory.FiberSegment
+ - core.inventory.Interrogator
+ - core.inventory.OpticalPath
+ - core.inventory.OpticalPathAnnotation
+ - core.inventory.OpticalMeasurement
+ - core.inventory.Acquisition
+ - core.inventory.Connector
+ - core.inventory.Splice
+ - core.inventory.Terminator
+ - core.inventory.Enclosure
+ - core.inventory.CouplingCondition
+ - core.inventory.Geometry
+ - core.inventory.DistanceMap
+ - core.inventory.CoordinateReferenceSystem
+ - core.inventory.Response
+ - core.inventory.CreationInfo
+ - core.inventory.ExternalResource
+ - core.inventory.ResolvedContext
+
+ - title: Models
+ desc: >
+ The base classes DASCore's documents are built from, and the registry
+ which lets a serialized document name the class it holds.
+ contents:
+ - models.DascoreBaseModel
+ - models.InventoryModel
+ - models.TimeRangedModel
+ - models.sensible_model_equals
+ - models.sensible_model_hash
+ - models.values_equal
+ - models.registry.register_model
+ - models.registry.registered_models
+
+ - title: Exceptions
+ desc: Exception classes raised by DASCore
+ contents:
+ - exceptions.DASCoreError
+ - exceptions.AttributeMergeError
+ - exceptions.ChunkError
+ - exceptions.CoordError
+ - exceptions.CoordDataError
+ - exceptions.CoordMergeError
+ - exceptions.CoordSortError
+ - exceptions.DASCorePluginError
+ - exceptions.DASVaderCompatibilityError
+ - exceptions.DependencyError
+ - exceptions.FilterValueError
+ - exceptions.IncompatiblePatchError
+ - exceptions.InvalidFiberFileError
+ - exceptions.InvalidFiberIOError
+ - exceptions.InvalidFileHandlerError
+ - exceptions.InvalidIndexError
+ - exceptions.InvalidIndexVersionError
+ - exceptions.InvalidInventoryError
+ - exceptions.InvalidModelTagError
+ - exceptions.InvalidSpoolError
+ - exceptions.InvalidSpoolQueryError
+ - exceptions.InvalidTimeRangeError
+ - exceptions.MissingOptionalDependencyError
+ - exceptions.MissingPatchError
+ - exceptions.ParameterError
+ - exceptions.PatchAttributeError
+ - exceptions.PatchBroadcastError
+ - exceptions.PatchConversionError
+ - exceptions.PatchCoordinateError
+ - exceptions.PatchError
+ - exceptions.RemoteCacheError
+ - exceptions.TimeError
+ - exceptions.UnitError
+ - exceptions.UnresolvedPatchError
+ - exceptions.UnknownExampleError
+ - exceptions.UnknownFiberFormatError
+ - exceptions.UnsupportedKeywordError
+
+jupyter: python3
diff --git a/docs/index.qmd b/index.qmd
similarity index 63%
rename from docs/index.qmd
rename to index.qmd
index bc6dea07c..b43de19e9 100644
--- a/docs/index.qmd
+++ b/index.qmd
@@ -1,35 +1,45 @@
---
+title: DASCore
execute:
warning: false
---
-{{< include ../readme.md >}}
-
-:::{.callout minimal="true"}
-Version-specific documentation builds are attached to the [release pages](https://github.com/DASDAE/dascore/releases).
-:::
-
-# Highlights
-
DASCore is a Python library for distributed acoustic sensing (DAS). It provides:
-1. IO support for [many DAS data formats](supported_formats.qmd)
+1. IO support for [many DAS data formats](about/supported_formats.qmd)
2. Common processing routines
3. Basic visualizations
-4. Plugin support for [known third-party namespaces](supported_plugins.qmd)
:::{.callout-note}
-DASCore is part of the [DAS Data Analysis Ecosystem (DASDAE)](https://dasdae.org).
+DASCore is part of the [DAS Data Analysis Ecosystem (DASDAE)](https://dasdae.org).
+Version-specific documentation builds are attached to the
+[release pages](https://github.com/DASDAE/dascore/releases).
:::
+# Installation
+
+Use pip or conda to install DASCore:
+
+```bash
+pip install dascore
+```
+
+```bash
+conda install dascore -c conda-forge
+```
+
+See [this recipe](recipes/docker_basic.qmd) for installation with Docker, and
+the [contributing docs](contributing/contributing.qmd) for development
+installations.
+
# Introductory usage
## Read a file
```{python}
import dascore as dc
-# Import fetch to read DASCore example files
-from dascore.utils.downloader import fetch
+# Import fetch to read DASCore example files
+from dascore.utils.downloader import fetch
# Fetch a sample file path from DASCore
file_path = fetch('terra15_das_1_trimmed.hdf5')
@@ -43,14 +53,11 @@ patch = spool[0]
## Working with a directory of DAS files
```{python}
-#| output: false
+#| eval: false
import dascore as dc
-# Write example DAS files to a local directory just to get a usable path.
-# To read a directory of DAS data stored on your machine,
-# simply replace the line below with:
-# directory_path = "/path/to/data/directory/"
-directory_path = dc.examples.spool_to_directory(dc.get_example_spool())
+# Point this at a directory of DAS files on your machine.
+directory_path = "/path/to/data/directory/"
spool = (
# Create a spool to interact with directory data
@@ -58,9 +65,9 @@ spool = (
# Index the directory contents
.update()
# Sub-select a specific time range
- .select(time=('2020-01-01', ...))
+ .select(time_min=('2020-01-01', ...))
# Specify chunk of the output patches
- .chunk(time=2, overlap=0.5)
+ .chunk(time=60, overlap=10)
)
```
@@ -90,7 +97,7 @@ out = (
# Decimate along time axis (keep every 8th sample)
patch.decimate(time=8)
# Detrend along the distance dimension
- .detrend(dim='distance')
+ .detrend(dim='distance')
# Apply 10Hz low-pass filter along time dimension
.pass_filter(time=(..., 10))
)
@@ -106,24 +113,17 @@ patch = dc.get_example_patch('example_event_2')
patch.viz.waterfall(show=True);
```
-# Installation
-
-Use pip or conda to install DASCore:
-
-```bash
-pip install dascore
-```
-
-```bash
-conda install dascore -c conda-forge
-```
+# Citation
-See [this recipe](recipes/docker_basic.qmd) for installation with Docker.
+If you use DASCore in your work, please cite the
+[Seismica article](https://seismica.library.mcgill.ca/article/view/1184):
-See the [contributing docs](contributing/contributing.qmd) for development installations.
+> Chambers, D., Jin, G., Tourei, A., Issah, A. H. S., Lellouch, A., Martin, E., Zhu, D., Girard, A., Yuan, S., Cullison, T., Snyder, T., Kim, S., Danes, N., Pnithan, N., Boltz, M. S. & Mendoza, M. M. (2024). DASCore: a Python Library for Distributed Fiber Optic Sensing. Seismica, 3(2).
# Feedback and Support
Use the [project discussions](https://github.com/DASDAE/dascore/discussions) to ask a question.
Use the [project issues](https://github.com/DASDAE/dascore/issues) to report an issue.
+
+[](https://github.com/DASDAE/dascore/graphs/contributors)
diff --git a/docs/notes/coordinate_internals.qmd b/notes/coordinate_internals.qmd
similarity index 98%
rename from docs/notes/coordinate_internals.qmd
rename to notes/coordinate_internals.qmd
index 267c0bba7..526fb5391 100644
--- a/docs/notes/coordinate_internals.qmd
+++ b/notes/coordinate_internals.qmd
@@ -1,5 +1,6 @@
---
title: Coordinate Internals
+description: "The internal coordinate model, including string-coordinate handling."
---
This note explains the current internal coordinate model in DASCore, with particular attention to exact values, segmented coordinates, summaries, and scan payloads.
diff --git a/docs/notes/dft_notes.qmd b/notes/dft_notes.qmd
similarity index 98%
rename from docs/notes/dft_notes.qmd
rename to notes/dft_notes.qmd
index f202f8bf5..aa818762e 100644
--- a/docs/notes/dft_notes.qmd
+++ b/notes/dft_notes.qmd
@@ -1,6 +1,9 @@
-# Fourier Transforms in DASCore
+---
+title: Fourier Transforms in DASCore
+description: "The reasoning behind DASCore's Fourier transform implementation."
+---
-These notes provide the reasoning for DASCore's [fourier](`dascore.transform.fourier`) module implementation.
+These notes provide the reasoning for DASCore's [fourier](/reference/index.qmd#transforms) module implementation.
## Summary {#sec-summary}
diff --git a/docs/notes/doc_strategy.qmd b/notes/doc_strategy.qmd
similarity index 90%
rename from docs/notes/doc_strategy.qmd
rename to notes/doc_strategy.qmd
index aa6d31349..f1a4b706d 100644
--- a/docs/notes/doc_strategy.qmd
+++ b/notes/doc_strategy.qmd
@@ -1,10 +1,11 @@
---
title: DASCore Documentation Strategy
+description: "How DASCore's documentation follows the Diataxis framework."
---
DASCore's documentation follows the [Diátaxis](https://diataxis.fr/) approach. In this system, developed by Daniele Procida, there are four types of documentation with different goals:
-
+
The sections of DASCore's documentation can be classified as follows:
diff --git a/docs/notes/patch_attrs.qmd b/notes/patch_attrs.qmd
similarity index 94%
rename from docs/notes/patch_attrs.qmd
rename to notes/patch_attrs.qmd
index 483de5b9b..136b30efc 100644
--- a/docs/notes/patch_attrs.qmd
+++ b/notes/patch_attrs.qmd
@@ -1,5 +1,6 @@
---
title: PatchAttrs
+description: "How PatchAttrs stores identity, coordinate, and data metadata for a patch."
---
[`PatchAttrs`](`dascore.core.attrs.PatchAttrs`) stores non-coordinate metadata about a [`Patch`](`dascore.Patch`), including measurement labels, instrument identity, data units, processing history, and format-specific fields. Coordinate values and summaries are deliberately owned elsewhere.
@@ -26,7 +27,7 @@ A stale or misleading `data_type` is much worse than an empty one.
| 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.
+DASCore-assigned `data_type` values should be snake_case and listed in `VALID_DATA_TYPES` in [`dascore.constants`](/reference/index.qmd). Correctness-critical code should prefer units, coordinates, and explicit validation.
### Patch functions
diff --git a/docs/notes/spool_chunking.qmd b/notes/spool_chunking.qmd
similarity index 99%
rename from docs/notes/spool_chunking.qmd
rename to notes/spool_chunking.qmd
index a2da0cd34..c450acd51 100644
--- a/docs/notes/spool_chunking.qmd
+++ b/notes/spool_chunking.qmd
@@ -1,5 +1,6 @@
---
title: Spool Chunking
+description: "How Spool.chunk plans and assembles chunked patch data."
---
`Spool.chunk` runs in two stages: a **planner** decides everything from metadata alone, and **assembly** loads, trims, and combines patch data only when a patch is requested. The code cells below execute against the real machinery, so this note fails the doc build if it drifts from the implementation.
diff --git a/docs/notes/spool_index.qmd b/notes/spool_index.qmd
similarity index 99%
rename from docs/notes/spool_index.qmd
rename to notes/spool_index.qmd
index 240cb891f..626792b24 100644
--- a/docs/notes/spool_index.qmd
+++ b/notes/spool_index.qmd
@@ -1,5 +1,6 @@
---
title: Spool Index
+description: "The shared metadata model and SQLite directory index behind spools."
---
Directory and in-memory spools use the same metadata model. The persisted directory index is one SQLite file named `.dascore_index.sqlite3`; in-memory spools use the same schema in an in-memory SQLite database. The index stores summaries and source identities, not patch data.
diff --git a/docs/notes/spool_selection.qmd b/notes/spool_selection.qmd
similarity index 98%
rename from docs/notes/spool_selection.qmd
rename to notes/spool_selection.qmd
index 51a66d027..51c968d0c 100644
--- a/docs/notes/spool_selection.qmd
+++ b/notes/spool_selection.qmd
@@ -1,5 +1,6 @@
---
title: Spool Selection
+description: "How Spool.select filters memory and directory spools."
---
`Spool.select` uses one selector model for memory and directory spools. Patch-list and directory spools compose selections in a `PatchCatalog`; ordinary metadata predicates are pushed into SQLite and remain lazy until contents, length, indexing, or iteration requires rows.
diff --git a/docs/notes/velocity_to_strain_rate.qmd b/notes/velocity_to_strain_rate.qmd
similarity index 95%
rename from docs/notes/velocity_to_strain_rate.qmd
rename to notes/velocity_to_strain_rate.qmd
index a48f6dcfe..a91c915d9 100644
--- a/docs/notes/velocity_to_strain_rate.qmd
+++ b/notes/velocity_to_strain_rate.qmd
@@ -1,5 +1,6 @@
---
title: "Velocity to Strain Rate"
+description: "Subtleties of converting velocity-format patches to strain rate."
execute:
warn: false
---
@@ -14,8 +15,8 @@ Although DASCore usually refers to these type of data as "velocity", since the u
There are two functions for converting velocity data to strain rate:
-1. [Patch.velocity_to_strain_rate](`dascore.Patch.velocity_to_strain_rate`)
-2. [Patch.velocity_to_strain_rate_edgeless](`dascore.Patch.velocity_to_strain_rate_edgeless`)
+1. [Patch.velocity_to_strain_rate](`dascore.transform.velocity_to_strain_rate`)
+2. [Patch.velocity_to_strain_rate_edgeless](`dascore.transform.velocity_to_strain_rate_edgeless`)
The first function uses a central difference scheme when possible, but also a forward/backwards difference scheme on the edges. This results in patch that is the same shape as the input patch, but, depending on the parameters, there may be some artefacts on the end channels. It only supports even `step_multiple` values which means the smallest gauge length is twice the distance step. It also supports higher order filters if the [findiff library](https://findiff.readthedocs.io/en/latest/) is installed.
diff --git a/pyproject.toml b/pyproject.toml
index e9ce5a14d..abb3f5a62 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -73,7 +73,8 @@ extras = [
]
docs = [
- "jinja2",
+ # renders utils docstrings onto the utilities page
+ "griffe",
"ipywidgets",
"tabulate",
# Quarto executes the {python} cells in docs/**.qmd through jupyter, so
@@ -93,6 +94,7 @@ test = [
"pytest-cov>=4",
"pre-commit",
"pytest",
+ "pyyaml", # test_doc_coverage.py reads great-docs.yml
"pytest-timeout",
"pytest-codeblocks",
"s3fs",
@@ -247,6 +249,9 @@ convention = "numpy"
# F401: a notebook often imports a name only used inside an IPython magic,
# which ruff cannot see, and its autofix would delete the import.
"*.ipynb" = ["D103", "F401"]
+# The doc-build hooks report progress on stdout (T201), and import dascore
+# only after re-execing with the environment it is installed in (PLC0415).
+"scripts/greatdocs_*.py" = ["T201", "PLC0415"]
[tool.pytest.ini_options]
norecursedirs = [
@@ -300,7 +305,13 @@ include = ["dascore"]
# These files lazily import optional or untyped modules (xarray, numba,
# h5py.h5r) that are not installed in the pre-commit hook's environment.
[[tool.ty.overrides]]
-include = ["dascore/compat.py", "dascore/utils/jit.py", "dascore/io/dasvader/utils.py"]
+# griffe (docs extra) is imported only while rendering the utilities page.
+include = [
+ "dascore/compat.py",
+ "dascore/utils/docs.py",
+ "dascore/utils/jit.py",
+ "dascore/io/dasvader/utils.py",
+]
[tool.ty.overrides.rules]
unresolved-import = "ignore"
diff --git a/docs/recipes/add_spatial_coordinates_to_patch.qmd b/recipes/add_spatial_coordinates_to_patch.qmd
similarity index 94%
rename from docs/recipes/add_spatial_coordinates_to_patch.qmd
rename to recipes/add_spatial_coordinates_to_patch.qmd
index 6d00dfde3..9bc368f84 100644
--- a/docs/recipes/add_spatial_coordinates_to_patch.qmd
+++ b/recipes/add_spatial_coordinates_to_patch.qmd
@@ -1,5 +1,6 @@
---
title: Add Spatial Coordinates
+description: "Attach tabulated spatial coordinates (e.g. from a tap test) to a patch dimension."
execute:
warning: false
---
diff --git a/recipes/contributing_to_documentation.qmd b/recipes/contributing_to_documentation.qmd
new file mode 100644
index 000000000..0a9b58a3e
--- /dev/null
+++ b/recipes/contributing_to_documentation.qmd
@@ -0,0 +1,67 @@
+---
+title: Contributing to Documentation
+description: "Build the docs locally and make changes to DASCore's documentation."
+---
+
+Here, we elaborate on how you can build new documentation or make changes to existing DASCore documentation.
+
+## Building/Editing documentation
+
+The site is built by [great-docs](https://posit-dev.github.io/great-docs/), which renders the narrative pages and the API reference with [Quarto](https://quarto.org/docs/get-started/).
+
+First, install Quarto. To ensure it is installed properly and to get the installation version:
+
+```bash
+quarto --version
+```
+
+If you have not already installed jupyter in your environment, install it using conda:
+
+```bash
+conda install jupyter
+```
+
+Then install great-docs:
+
+```bash
+pip install "great-docs>=0.16"
+```
+
+Go to the DASCore directory on your machine and build the site:
+
+```bash
+cd dascore
+great-docs build
+```
+
+:::{.note}
+This will take several minutes the first time you run it. After that, the results are cached and only the changed files are re-rendered.
+:::
+
+:::{.callout-warning}
+The cache is keyed on the `.qmd` source, not on the DASCore code a page imports. A page which builds its content by calling DASCore — the utilities page, the supported formats and plugins tables, the example listings — will keep showing its previous output after you change the underlying function. Delete the page's cache entry to force it to run again:
+
+```bash
+rm -rf _freeze/utilities
+```
+
+A fresh checkout has no cache, so CI never sees stale output; this only bites while developing locally.
+:::
+
+The built site lands in `great-docs/_site`. To read it in a browser:
+
+```bash
+great-docs preview
+```
+
+## Documenting new public API
+
+The API reference is curated rather than generated: `great-docs.yml` lists the objects it documents, grouped by task. That means adding a public function does not add it to the docs, so `tests/test_doc_coverage.py` walks the public functions and classes DASCore defines and fails when one is represented nowhere. Its failure message names the missing objects and your options, which are to add a `reference:` entry, let it be rendered on the utilities page by putting it in `dascore.utils`, or record it as deliberately undocumented in that test file with a reason.
+
+The same test checks that reference entries still resolve, that class `members:` lists name real methods, and that every public method and property of a documented class is covered. Run it on its own while editing docs:
+
+```bash
+pytest tests/test_doc_coverage.py
+```
+
+Narrative pages live in the top-level section directories (`tutorial/`, `recipes/`, `notes/`, `contributing/`, `about/`), and `index.qmd` is the landing page. Adding a `.qmd` file to one of those directories adds a page to that section. The API reference is generated from DASCore's docstrings, so changing a docstring is enough to change the reference; which objects appear there, along with the site's structure and styling, is configured in `great-docs.yml`.
diff --git a/docs/recipes/correlate.qmd b/recipes/correlate.qmd
similarity index 92%
rename from docs/recipes/correlate.qmd
rename to recipes/correlate.qmd
index 21182d8be..24c752107 100644
--- a/docs/recipes/correlate.qmd
+++ b/recipes/correlate.qmd
@@ -1,5 +1,6 @@
---
title: Correlate
+description: "Cross-correlate a channel/time sample against all others with the correlate module."
execute:
warning: false
---
diff --git a/docs/recipes/despiking.qmd b/recipes/despiking.qmd
similarity index 96%
rename from docs/recipes/despiking.qmd
rename to recipes/despiking.qmd
index 5dd76203b..c949934c0 100644
--- a/docs/recipes/despiking.qmd
+++ b/recipes/despiking.qmd
@@ -1,5 +1,6 @@
---
title: "Despiking"
+description: "Remove spikes from DAS data."
execute:
warning: false
---
@@ -11,7 +12,7 @@ This recipe demonstrates how to remove spikes from DAS data.
The Hampel filter is effective for removing outliers (spikes) in data by identifying values that deviate significantly from the local median in terms of median absolute deviation [(MAD)](https://en.wikipedia.org/wiki/Median_absolute_deviation). This makes it particularly useful for cleaning DAS data that may contain various types of noise spikes.
-DASCore provides the [`Patch.hampel_filter`](`dascore.Patch.hampel_filter`). The filter works by:
+DASCore provides the [`Patch.hampel_filter`](`dascore.proc.hampel_filter`). The filter works by:
1. Computing the local median within a sliding window (optionally along multiple dimensions)
2. Calculating the median absolute deviation (MAD) within the same window
diff --git a/docs/recipes/docker_basic.qmd b/recipes/docker_basic.qmd
similarity index 92%
rename from docs/recipes/docker_basic.qmd
rename to recipes/docker_basic.qmd
index ee43fe3f6..2ca6c53be 100644
--- a/docs/recipes/docker_basic.qmd
+++ b/recipes/docker_basic.qmd
@@ -1,5 +1,6 @@
---
title: Using DASCore with Docker
+description: "Run DASCore inside a Docker container."
execute:
warning: false
---
diff --git a/docs/recipes/edge_effects.qmd b/recipes/edge_effects.qmd
similarity index 88%
rename from docs/recipes/edge_effects.qmd
rename to recipes/edge_effects.qmd
index 4c5f72349..f4dd5e8db 100644
--- a/docs/recipes/edge_effects.qmd
+++ b/recipes/edge_effects.qmd
@@ -1,5 +1,6 @@
---
title: Taper Edge Effects
+description: "Use tapering to manage edge effects from filtering and other processing."
execute:
warning: false
---
diff --git a/docs/recipes/external_conversion.qmd b/recipes/external_conversion.qmd
similarity index 95%
rename from docs/recipes/external_conversion.qmd
rename to recipes/external_conversion.qmd
index 804f0f6de..806a78652 100644
--- a/docs/recipes/external_conversion.qmd
+++ b/recipes/external_conversion.qmd
@@ -1,5 +1,6 @@
---
title: Patch Conversions
+description: "Convert patches to and from other libraries' data structures."
execute:
warning: false
---
diff --git a/docs/recipes/fk.qmd b/recipes/fk.qmd
similarity index 89%
rename from docs/recipes/fk.qmd
rename to recipes/fk.qmd
index 7321b3abb..d29c3da30 100644
--- a/docs/recipes/fk.qmd
+++ b/recipes/fk.qmd
@@ -1,5 +1,6 @@
---
title: F-K Transform and Filtering
+description: "Apply frequency-wavenumber (F-K) transforms and filtering."
execute:
warning: False
---
@@ -52,7 +53,7 @@ ax.set_ylim(-.2, .2);
## Slope Filtering
-One advantage of the F-K transform is the ability to manipulate signals based on their apparent velocities. [`Patch.slope_filter`](`dascore.Patch.slope_filter`) can be used for this purpose.
+One advantage of the F-K transform is the ability to manipulate signals based on their apparent velocities. [`Patch.slope_filter`](`dascore.proc.slope_filter`) can be used for this purpose.
For example, given the first example event, we can apply a slope filter whose range covers reasonable seismic velocities. The `filt` parameter specifies the slope (apparent velocities). It is a 4 length sequence of the form [va, vb, vc, vd] where velocities between `vb` and `vc` are unchanged (or set to zero if `invert=True`) and values between `va` and `vb`, as well as those between `vc` and `vd`, are tapered.
@@ -69,7 +70,7 @@ It's important to remember that *apparent* velocities (>= velocity) are filtered
### Phase Separation
-Another application of [`slope_filter`](`dascore.Patch.slope_filter`) is to separate P/S waves. In this case, the P velocity is about 4500 m/s and the S velocity is about 2700 m/s. The following code highlights S waves, but also introduces some artifacts:
+Another application of [`slope_filter`](`dascore.proc.slope_filter`) is to separate P/S waves. In this case, the P velocity is about 4500 m/s and the S velocity is about 2700 m/s. The following code highlights S waves, but also introduces some artifacts:
```{python}
# velocities in m/s (distance in meters, time in seconds)
diff --git a/docs/recipes/how_to_contribute.qmd b/recipes/how_to_contribute.qmd
similarity index 89%
rename from docs/recipes/how_to_contribute.qmd
rename to recipes/how_to_contribute.qmd
index 1e1e7ddf1..ff22d27a8 100644
--- a/docs/recipes/how_to_contribute.qmd
+++ b/recipes/how_to_contribute.qmd
@@ -1,12 +1,13 @@
---
title: "How to Contribute?"
+description: "A step-by-step guide to making your first contribution to DASCore."
---
On this page, we provide a step-by-step procedure on how you can start contributing to DASCore.
-# DASDAE developers
+## DASDAE developers
-## Step 1: Install DASCore in development mode
+### Step 1: Install DASCore in development mode
For the first time using DASCore in development mode, or if a new release is out, you need to install DASCore as it is mentioned [here](https://dascore.org/contributing/dev_install.html). Otherwise, you just need to activate the environment:
@@ -33,7 +34,7 @@ pytest
```
-## Step 2: Create a new branch to work on
+### Step 2: Create a new branch to work on
To create a new branch:
@@ -75,25 +76,25 @@ git commit -m "your commit"
git push origin branch_name
```
-## Step 3: Create a Pull Request
+### Step 3: Create a Pull Request
Navigate to the DASCore repository on GitHub, and you should see a notification about your recent push. Click the "Compare & pull request" button to create a new pull request.
-# DASDAE users
+## DASDAE users
If you'd like to contribute to DASCore as a user, you should first fork the DASCore repository. Forking a repository allows you to freely experiment with changes without affecting the original project. Below is a detailed guide on how to do this. [This GitHub documentation](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) might be beneficial to review as well.
-## Step 1: Fork the repository
+### Step 1: Fork the repository
Go to the [DASCore repository](https://github.com/DASDAE/dascore) and click on "Fork".
-## Step 2: Clone your fork
+### Step 2: Clone your fork
Once you have forked the repository, you need to clone it to your local machine to start making changes. Then navigate to your local repository:
```bash
cd dascore
```
-## Step 3: Set Upstream Repository
+### Step 3: Set Upstream Repository
Add the original DASCore repository as an upstream repository, which will be useful for keeping your fork up to date with the original project:
```bash
@@ -110,7 +111,7 @@ You should see the original repository as `upstream` and your fork as `origin`.
Then, create a new branch and start making changes to the repository.
-## Step 4: Create a new branch to work on
+### Step 4: Create a new branch to work on
To create a new branch:
@@ -152,6 +153,6 @@ git commit -m "your commit"
git push origin branch_name
```
-## Step 5: Create a Pull Request
+### Step 5: Create a Pull Request
Navigate to your project's GitHub repository, for instance, https://github.com//dascore, and click "Contribute" and then "Open a pull request". Then, create a pull request and provide a detailed title and description for your changes, explaining the rationale behind your pull request.
diff --git a/docs/recipes/low_freq_proc.qmd b/recipes/low_freq_proc.qmd
similarity index 98%
rename from docs/recipes/low_freq_proc.qmd
rename to recipes/low_freq_proc.qmd
index 38eacd007..7afc546c1 100644
--- a/docs/recipes/low_freq_proc.qmd
+++ b/recipes/low_freq_proc.qmd
@@ -1,5 +1,6 @@
---
title: "Low-Frequency Processing"
+description: "Apply efficient low-frequency (LF) processing to a spool of DAS data."
---
This recipe demonstrates how DASCore can be used to apply low-frequency (LF) processing to a spool of DAS data. LF processing helps efficiently downsample the entire spool.
diff --git a/docs/recipes/parallelization.qmd b/recipes/parallelization.qmd
similarity index 95%
rename from docs/recipes/parallelization.qmd
rename to recipes/parallelization.qmd
index 67fc92352..343be6401 100644
--- a/docs/recipes/parallelization.qmd
+++ b/recipes/parallelization.qmd
@@ -1,12 +1,13 @@
---
title: "Parallel Processing"
+description: "Strategies for parallelizing embarrassingly parallel spool workflows."
execute:
eval: false
---
This recipe shows a few strategies to parallelize "embarrassingly parallel" spool processing workflows.
-# Processes and Threads
+## Processes and Threads
[`Spool.map`](`dascore.core.spool.BaseSpool.map`) is the easiest way to process patches in a spool in parallel. Here is an example using the Python standard library module [concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html):
```{python}
@@ -29,11 +30,11 @@ The `ThreadPoolExecutor` from the same module also works. On a free-threaded (no
There are two downsides to this approach. First, if the patches aren't chunked adequately it may exhaust the available memory. Second, it will only work on a single machine. The next section presents a more scalable option.
-# Thread Safety
+## Thread Safety
DASCore's core test suite — no optional dependencies, no network tests — runs on a free-threaded CPython build with the GIL disabled. The guarantees below are what that supports; they apply equally to threads on a standard build.
-## What threads may share
+### What threads may share
Patches are immutable, so any number of threads may read the same patch. Spools may also be read concurrently: iterating, selecting, chunking, indexing and taking their length are safe from several threads at once, because the underlying metadata caches are synchronized internally.
@@ -43,7 +44,7 @@ The process-wide registries take care of themselves. The file-format (`FiberIO`)
Remote files are cached per resource. Threads asking for the same remote file take turns, so it downloads once and the rest use the result; different files download at the same time.
-## What threads may not share
+### What threads may not share
State-changing calls follow a single-writer model. Updating a directory spool, adding patches to an in-memory spool and [`clear_remote_file_cache`](`dascore.utils.remote_io.clear_remote_file_cache`) should each run from one thread, with no other thread reading the same object meanwhile. DASCore keeps its own structures consistent through such a change, but it does not make a reader see a coherent before-or-after snapshot of it.
@@ -51,17 +52,17 @@ An open file handle belongs to one IO operation. DASCore opens each resource onc
Third-party `FiberIO` plugins are responsible for their own thread safety. DASCore synchronizes the registry that holds them and serializes their import, but a plugin which keeps mutable state of its own must guard it.
-## Configuration in threads
+### Configuration in threads
Configuration has two tiers, described in [runtime configuration](../tutorial/configuration.qmd). `set_config` changes the process-wide base and is visible from every thread. `config_context` overrides the config for the current context only, and a newly started thread begins with a fresh context, so a scoped override does not automatically reach threads started inside it.
[`Spool.map`](`dascore.core.spool.BaseSpool.map`) handles this for you: it binds the configuration active when `map` is called and re-applies it inside each worker, for thread pools and process pools alike. When starting threads yourself, either set the configuration permanently before starting them or re-apply the scoped override inside each thread.
-# MPI4Py
+## MPI4Py
This section shows how to use the "mpi4py" library to parallelize dascore code.
-## Installation
+### Installation
First, make sure you have installed DASCore on your machine. See [DASCore Installation](https://dascore.org/#:~:text=0.2%29%3B-,Installation). Secondly, you need to properly install the mpi4py library. After installing and loading the [Open MPI](https://docs.open-mpi.org/en/v5.0.x/installing-open-mpi/quickstart.html) module on your machine (e.g., on Linux: `load module to/mpi/openmpi/gcc/compiler/path`), install [mpi4py](https://pypi.org/project/mpi4py/). It might be easier to install using conda-forge as below:
@@ -72,7 +73,7 @@ conda install -c conda-forge mpi4py openmpi
Please note that this procedure is tested for Python 3.11 and Open MPI GCC 3.1.3
-## Parallel script
+### Parallel script
Here is an example for parallelization of Patches over processors:
@@ -110,7 +111,7 @@ comm.barrier()
sys.exit(0)
```
-## Run the script
+### Run the script
If you like to run the `mpi_spool.py` script using `n = 4` processors (which means each processor will run the script separately), you can use:
diff --git a/docs/recipes/plotting_channel_number.qmd b/recipes/plotting_channel_number.qmd
similarity index 92%
rename from docs/recipes/plotting_channel_number.qmd
rename to recipes/plotting_channel_number.qmd
index d2a193cbc..32c9ad89b 100644
--- a/docs/recipes/plotting_channel_number.qmd
+++ b/recipes/plotting_channel_number.qmd
@@ -1,5 +1,6 @@
---
title: Plot Channel Number
+description: "Plot channel number instead of distance or depth."
execute:
warning: false
---
diff --git a/docs/recipes/real_time_proc.qmd b/recipes/real_time_proc.qmd
similarity index 98%
rename from docs/recipes/real_time_proc.qmd
rename to recipes/real_time_proc.qmd
index f3fb72b4a..db2dfd7cd 100644
--- a/docs/recipes/real_time_proc.qmd
+++ b/recipes/real_time_proc.qmd
@@ -1,5 +1,6 @@
---
title: "Real-Time Processing"
+description: "Process streaming DAS data in near real time."
execute:
eval: false
---
diff --git a/docs/recipes/smoothing.qmd b/recipes/smoothing.qmd
similarity index 63%
rename from docs/recipes/smoothing.qmd
rename to recipes/smoothing.qmd
index ac62bcdf7..2d857631e 100644
--- a/docs/recipes/smoothing.qmd
+++ b/recipes/smoothing.qmd
@@ -1,5 +1,6 @@
---
title: "Patch Smoothing"
+description: "Compare several patch smoothing strategies."
execute:
warning: false
---
@@ -8,13 +9,13 @@ This recipe compares several smoothing strategies.
A few patch methods useful for smoothing are:
-- [`Patch.rolling`](`dascore.Patch.rolling`)
-- [`Patch.savgol_filter`](`dascore.Patch.savgol_filter`)
-- [`Patch.gaussian_filter`](`dascore.Patch.gaussian_filter`)
+- [`Patch.rolling`](`dascore.proc.rolling`)
+- [`Patch.savgol_filter`](`dascore.proc.savgol_filter`)
+- [`Patch.gaussian_filter`](`dascore.proc.gaussian_filter`)
:::{.callout-note}
-[`Patch.rolling`](`dascore.Patch.rolling`) is quite flexible and can be used for many different processing tasks. However, as mentioned in the [rolling section of the tutorial](/tutorial/processing.qmd#rolling), [`Patch.rolling`](`dascore.Patch.rolling`) includes `NaN` entries in the output due to edge effects. This can require some additional thought to properly deal with.
+[`Patch.rolling`](`dascore.proc.rolling`) is quite flexible and can be used for many different processing tasks. However, as mentioned in the [rolling section of the tutorial](/tutorial/processing.qmd#rolling), [`Patch.rolling`](`dascore.proc.rolling`) includes `NaN` entries in the output due to edge effects. This can require some additional thought to properly deal with.
The other methods mentioned above deal with edge effects differently (governed by the `mode` parameter) and by default don't include `NaN` entries.
:::
@@ -35,7 +36,7 @@ ax.set_title("un-smoothed patch");
## Rolling
-[`Patch.rolling`](`dascore.Patch.rolling`) can be used for applying aggregation functions to rolling windows along one dimension of the `Patch` instance. For instance, to apply a rolling mean to the time axis:
+[`Patch.rolling`](`dascore.proc.rolling`) can be used for applying aggregation functions to rolling windows along one dimension of the `Patch` instance. For instance, to apply a rolling mean to the time axis:
```{python}
smoothed_patch = (
@@ -80,12 +81,12 @@ ax = nan_patch.viz.waterfall()
ax.set_title("NaNs in Patch");
```
-Which can be dropped with [Patch.dropna](`dascore.Patch.dropna`).
+Which can be dropped with [Patch.dropna](`dascore.proc.dropna`).
## Savgol filter
-[`Patch.savgol_filter`](`dascore.Patch.savgol_filter`) uses [SciPy's savgol_filter](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.savgol_filter.html), which is an implementation of the [Savitzky-Golay Filter](https://en.wikipedia.org/wiki/Savitzky%E2%80%93Golay_filter) to apply smoothing along a single dimension.
+[`Patch.savgol_filter`](`dascore.proc.savgol_filter`) uses [SciPy's savgol_filter](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.savgol_filter.html), which is an implementation of the [Savitzky-Golay Filter](https://en.wikipedia.org/wiki/Savitzky%E2%80%93Golay_filter) to apply smoothing along a single dimension.
```{python}
smoothed_patch = (
@@ -112,7 +113,7 @@ ax.set_title("savgol time and distance");
## Gaussian filter
-[`Patch.gaussian_filter`](`dascore.Patch.gaussian_filter`) uses [SciPy's gaussian_filter](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html) to apply a gaussian smoothing operator to the patch. Note that the keyword arguments here specify standard deviation, and the `truncate` keyword determines how many standard deviations are included in the smoothing kernel.
+[`Patch.gaussian_filter`](`dascore.proc.gaussian_filter`) uses [SciPy's gaussian_filter](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html) to apply a gaussian smoothing operator to the patch. Note that the keyword arguments here specify standard deviation, and the `truncate` keyword determines how many standard deviations are included in the smoothing kernel.
```{python}
smoothed_patch = patch.gaussian_filter(
diff --git a/scripts/_index_api.py b/scripts/_index_api.py
deleted file mode 100644
index 5e872db2b..000000000
--- a/scripts/_index_api.py
+++ /dev/null
@@ -1,267 +0,0 @@
-"""A script to create an index of the structure of the DASCore package."""
-
-from __future__ import annotations
-
-import inspect
-import os
-from collections import defaultdict
-from importlib import import_module
-from pathlib import Path
-from types import FunctionType, MethodType, ModuleType
-from typing import Literal
-
-
-def _unwrap_obj(obj):
- """Unwrap a decorated object."""
- while getattr(obj, "__wrapped__", None) is not None:
- obj = obj.__wrapped__
- return obj
-
-
-def _get_file_path(obj):
- """Try to get the file of a python object."""
- obj = _unwrap_obj(obj)
- try:
- path = inspect.getfile(obj)
- except TypeError:
- path = ""
- return Path(path)
-
-
-def _is_environment_path(path) -> bool:
- """
- Return True if the path belongs to an environment nested in the project.
-
- Virtual environments (e.g. .venv) created inside the repository would
- otherwise be traversed as if their contents were part of the project.
- """
- excluded = {"site-packages", ".venv", "venv", ".pixi", ".tox", ".nox"}
- return bool(excluded.intersection(Path(path).parts))
-
-
-def _get_base_address(path, base_path):
- """
- Get the base address inherent in the path.
-
- For example, dascore/core/patch.py should return
-
- dascore.core.patch
- """
- if _is_environment_path(path):
- return ""
- try:
- out = Path(path).relative_to(Path(base_path))
- except ValueError:
- return ""
- new = str(out).replace("/__init__.py", "").replace(".py", "")
- return new.replace("/", ".")
-
-
-def _get_address(obj, rel_path):
- """Get the address for an object."""
- base_list = (
- str(rel_path)
- .replace(f"{os.sep}__init__.py", "")
- .replace(".py", "")
- .split(os.sep)
- )
- if not isinstance(obj, ModuleType) and hasattr(obj, "__name__"):
- name_list = [obj.__name__]
- else:
- name_list = []
- return ".".join(base_list + name_list)
-
-
-def _yield_get_submodules(obj, base_path):
- """Dynamically load submodules that may not have been imported."""
- path = Path(inspect.getfile(obj))
- if not isinstance(obj, ModuleType) or path.name != "__init__.py":
- return
- submodules = path.parent.glob("*")
- for submod_path in submodules:
- is_dir = submod_path.is_dir()
- is_init = submod_path.name.endswith("__init__.py")
- # this is a directory, look for corresponding __init__.py
- if is_dir and (submod_path / "__init__.py").exists():
- mod_name = str(submod_path.relative_to(base_path)).replace(os.sep, ".")
- mod = import_module(mod_name)
- yield mod_name, mod
- elif submod_path.name.endswith(".py") and not is_init:
- mod_name = (
- str(submod_path.relative_to(base_path))
- .replace(".py", "")
- .replace(os.sep, ".")
- )
- mod = import_module(mod_name)
- yield mod_name, mod
-
-
-def parse_project(obj, key=None):
- """Parse the project create dict of data and data_type."""
-
- def get_type(
- obj, parent_is_class=False
- ) -> Literal["module", "function", "method", "class"] | None:
- """Return a string of the type of object."""
- obj = _unwrap_obj(obj)
- if isinstance(obj, ModuleType):
- return "module"
- elif isinstance(obj, MethodType):
- return "method"
- elif isinstance(obj, FunctionType):
- # since this is a class not instance we need this check.
- if parent_is_class:
- return "method"
- else:
- return "function"
- elif isinstance(obj, type):
- return "class"
- return None
-
- def extract_data(obj, parent_is_class):
- """
- Make lists of attributes, methods, etc.
-
- These can be feed to render templates.
- """
- try:
- sig = inspect.signature(obj)
- except (TypeError, ValueError):
- sig = None
-
- docstr = inspect.getdoc(obj) or ""
- dtype = get_type(obj, parent_is_class)
- data = defaultdict(list)
- data["docstring"] = docstr
- data["signature"] = sig
- data["data_type"] = dtype
- data["short_description"] = docstr.split("\n")[0]
- data["object"] = obj
- # get sub-modules, methods, functions, etc. (just one level deep)
-
- subs = list(inspect.getmembers(obj)) + list(
- _yield_get_submodules(obj, base_path)
- )
- for name, sub_obj in subs:
- if name.startswith("_"):
- continue
- sub_obj = _unwrap_obj(sub_obj)
- sub_dtype = get_type(sub_obj, dtype == "class")
- # for modules, skip entities that aren't children
- if dtype == "module":
- path, sub_path = _get_file_path(obj), _get_file_path(sub_obj)
- if str(path).replace("/__init__.py", "") not in str(sub_path):
- continue
- data[sub_dtype].append(str(id(sub_obj)))
-
- return data
-
- def get_data(obj, key, base_path, parent_is_class):
- """Get data from object."""
- path = inspect.getfile(obj)
- base_address = _get_base_address(path, base_path)
- data = extract_data(obj, parent_is_class)
- data["base_path"] = Path(base_path)
- data["path"] = Path(path)
- data["key"] = key
- data["name"] = key.split(".")[-1]
- data["base_address"] = base_address
- return data
-
- def traverse(obj, data_dict, base_path, key=None, parent_is_class=False):
- """Traverse tree, populate data_dict."""
- obj = _unwrap_obj(obj)
- obj_id = str(id(obj))
- path = _get_file_path(obj)
-
- # this is something outside of dascore or we have already seen it.
- if str(base_path) not in str(path) or obj_id in data_dict:
- return
- # skip contents of environments (e.g. .venv) nested in the project.
- if _is_environment_path(path):
- return
- # load all the modules first
- if isinstance(obj, ModuleType):
- key = _get_address(obj, path.relative_to(base_path))
- data_dict[obj_id] = get_data(obj, key, base_path, parent_is_class)
- for _, mod in _yield_get_submodules(obj, base_path):
- traverse(mod, data_dict, base_path)
- for name, obj in inspect.getmembers(obj):
- # skip private members; we don't document them.
- if name.startswith("_"):
- continue
- # recurse non-private methods
- traverse(obj, data_dict, base_path, f"{key}.{name}", False)
- # then handle non-modules
- else:
- path = inspect.getfile(obj)
- base_address = _get_base_address(path, base_path)
-
- # this is referenced outside of its base address, skip this one.
- # A prefix test rather than a substring one: the address of
- # dascore/core/inventory.py is a substring of every key under
- # dascore/core/inventory_loader.py, so a module named after
- # another would claim as its own everything it merely imports.
- if key != base_address and not key.startswith(f"{base_address}."):
- return
-
- data_dict[str(id(obj))] = get_data(obj, key, base_path, parent_is_class)
- # recurse attributes and methods of classes
- if inspect.isclass(obj):
- for sub_name, sub_obj in inspect.getmembers(obj):
- if sub_name.startswith("_") or not callable(sub_obj):
- continue
- sub_path = _get_file_path(sub_obj)
- if str(base_path) not in str(sub_path):
- continue
- if _is_environment_path(sub_path):
- continue
- sub_key = f"{key}.{sub_name}"
- # make sure this is where the method is defined else skip
- if str(sub_path) != str(path):
- continue
- traverse(sub_obj, data_dict, base_path, sub_key, True)
-
- base_path = Path(_get_file_path(obj)).parent.parent
- data_dict = {}
- traverse(obj, data_dict, base_path)
-
- return data_dict
-
-
-def get_alias_mapping(module, key=None):
- """Return a dict of {object_path: id} to construct cross refs."""
-
- def traverse_simple(obj, key, data_dict, base_path, allow_base=False):
- """Traverse the tree and write out markdown."""
- obj = _unwrap_obj(obj)
- obj_id = str(id(obj))
- path = _get_file_path(obj)
- # don't let base module be re-indexed
- if obj is base_mod and not allow_base:
- return
- # skip things outside of this module
- if not _get_base_address(path, base_path):
- return
- # need to ensure all sub-modules are loaded.
- if isinstance(obj, ModuleType):
- for _, mod in _yield_get_submodules(obj, base_path):
- attach_name = f"{mod.__name__.split('.')[-1]}"
- setattr(obj, attach_name, mod)
-
- data_dict[key] = obj_id
- for member_name, member in inspect.getmembers(obj):
- if member_name.startswith("_"):
- continue
- new_key = ".".join([key, member_name])
- if f".{new_key.split('.')[-1]}" in key:
- continue
- traverse_simple(member, new_key, data_dict, base_path)
-
- data_dict = {}
- key = key or getattr(module, "__name__", None)
- base_path = Path(_get_file_path(module)).parent.parent
- # define base module and base key to not allow recursion through them.
- base_mod = module
- traverse_simple(module, key, data_dict, base_path, allow_base=True)
- return data_dict
diff --git a/scripts/_qmd_builder.py b/scripts/_qmd_builder.py
deleted file mode 100644
index 6695dfbf6..000000000
--- a/scripts/_qmd_builder.py
+++ /dev/null
@@ -1,103 +0,0 @@
-"""A script to build quartos main config file."""
-
-from __future__ import annotations
-
-import os
-from pathlib import Path
-
-from _render_api import get_template
-
-import dascore as dc
-
-API_PATH = Path(__file__).absolute().parent.parent / "docs" / "api"
-
-# separation for each level of toc tree
-LEVEL_SEP = " "
-
-
-def _build_content_string(path, api_path):
- """Build a content string."""
- out = [
- f"- text: {path.with_suffix('').name}",
- f" href: {path.relative_to(api_path)}",
- ]
- return out
-
-
-def _build_section_string(path, api_path):
- """Build a string for entire sections."""
- out = [
- f"- section: {path.with_suffix('').name}",
- f" href: {path.relative_to(api_path)}",
- " contents:",
- ]
- return out
-
-
-def _get_level(path, base_path):
- """Get the level of directory nested for path from base_path."""
- level = len(str(path.relative_to(base_path)).split(os.sep)) - 1
- return level
-
-
-def build_amp_toc_tree(api_path=API_PATH):
- """Build the toc tree for the API."""
- # get all sub directories
- base_path = api_path.parent
- sub_dirs = sorted(x for x in api_path.rglob("*") if x.is_dir())
- sub_dir_set = set(sub_dirs)
- out = []
- # iterate and make contents
- for dir_path in sub_dirs:
- # this is an un-cleaned up quarto dir from rendering
- bad_endings = ["execute-results", "_files"]
- bad_ending = any(dir_path.name.endswith(x) for x in bad_endings)
- bad_p_ending = any(dir_path.parent.name.endswith(x) for x in bad_endings)
- # the expected path of the qmd file related to this directory.
- section_qmd = dir_path.with_suffix(".qmd")
- if (bad_ending or bad_p_ending) and not section_qmd.exists():
- continue
- assert section_qmd.exists()
- level = _get_level(section_qmd, api_path)
- # see how deep we are in toc tree for determine spaces needed
- section_list = _build_section_string(section_qmd, base_path)
- for val in section_list:
- out.append(LEVEL_SEP * (level) + val)
- # now go through contents
- contents = sorted(
- x for x in dir_path.glob("*.qmd") if x.with_suffix("") not in sub_dir_set
- )
- for content in contents:
- content_list = _build_content_string(content, base_path)
- for val in content_list:
- out.append(LEVEL_SEP * (level + 1) + val)
- return out
-
-
-def _get_dascore_title():
- """Get the DASCore title with the docs version."""
- doc_version = os.environ.get("DASCORE_DOC_VERSION")
- if doc_version is not None:
- vstr = doc_version
- else:
- version_str = str(dc.__version__)
- if "dev" not in version_str:
- vstr = version_str
- else:
- vstr = version_str.split("+")[0]
- return f"DASCore ({vstr})"
-
-
-def create_quarto_qmd():
- """Create the _quarto.yml file."""
- temp = get_template("_quarto.yml")
- version_str = _get_dascore_title()
- api_toc_tree = build_amp_toc_tree()
- out = temp.render(dascore_version_str=version_str, api_toc_tree=api_toc_tree)
- path = Path(__file__).parent.parent / "docs" / "_quarto.yml"
- with path.open("w") as fi:
- fi.write(out)
-
-
-if __name__ == "__main__":
- create_quarto_qmd()
diff --git a/scripts/_render_api.py b/scripts/_render_api.py
deleted file mode 100644
index 3f7b9a246..000000000
--- a/scripts/_render_api.py
+++ /dev/null
@@ -1,639 +0,0 @@
-"""Create the html tables for parameters and such from dataframes."""
-
-from __future__ import annotations
-
-import hashlib
-import inspect
-import json
-import os
-import typing
-from collections import defaultdict
-from functools import cache
-from itertools import pairwise
-from pathlib import Path
-
-import pandas as pd
-from jinja2 import Environment, FileSystemLoader
-
-RENDER_FUNCS = {}
-DOC_PATH = Path(__file__).absolute().parent.parent / "docs"
-API_DOC_PATH = DOC_PATH / "api"
-API_DOC_PATH.mkdir(exist_ok=True, parents=True)
-TEMPLATE_PATH = DOC_PATH / "_templates"
-GITHUB_PATH = "https://github.com"
-GITHUB_REPOSITORY = "DASDAE/dascore"
-GITHUB_REF = "/master"
-
-
-@cache
-def get_env():
- """Get the template environment."""
- template_path = Path(__file__).absolute().parent / "_templates"
- env = Environment(loader=FileSystemLoader(template_path))
- return env
-
-
-@cache
-def get_template(name):
- """Get the template for rendering tables."""
- env = get_env()
- template = env.get_template(name)
- return template
-
-
-def _simple_plural(text):
- """Return plural form of string."""
- if text.endswith("s"):
- return text + "es"
- return text + "s"
-
-
-def sha_256(path_or_str: Path | str) -> str:
- """Get the Sha256 hash of a file or a string."""
- if isinstance(path_or_str, Path) and Path(path_or_str).exists():
- with open(path_or_str) as fi:
- path_or_str = fi.read()
- return hashlib.sha256(str(path_or_str).encode("utf-8")).hexdigest()
-
-
-def build_table(df: pd.DataFrame, caption=None):
- """An opinionated function to make a dataframe into html table."""
- df = df.drop_duplicates()
- template = get_template("table.html")
- columns = [x.capitalize() for x in df.columns]
- rows = df.to_records(index=False).tolist()
- out = template.render(columns=columns, rows=rows, caption=caption)
- return out
-
-
-def is_it_subclass(obj, cls):
- """A more intelligent issubclass that doesn't through TypeErrors."""
- try:
- return issubclass(obj, cls)
- except TypeError:
- return False
-
-
-def _deduplicate_list(seq):
- """Return a list with duplicate removed, preserves order."""
- seen = set()
- return [x for x in seq if not (x in seen or seen.add(x))]
-
-
-def unpact_annotation(obj, data_dict, address_dict) -> str:
- """Convert an annotation to string with linking."""
- str_id = str(id(obj))
- str_rep = str(obj)
- # this is a basic type, just get its name.
- if is_it_subclass(obj, (str, int, float)):
- out = str_rep.replace("", "").replace("'", "")
- # this is a forward ref, strip out name and hope for the best ;)
- elif isinstance(obj, typing.ForwardRef):
- # todo: this needs to be more generic, but forward refs are hard!
- # just assume the bound string is resolvable
- key = str(obj).split("('")[-1].replace("')", "")
- name = key.split(".")[-1]
- # dc. is the same as dascore.
- if key.startswith("dc."):
- key = f"dascore.{key[3:]}"
- return f"[{name}](`{key}`)"
- # Array-like thing from numpy #TODO improve this
- elif "numpy.typing" in str_rep:
- return "ArrayLike"
- # this is a union, unpack it
- elif str_rep.startswith("typing.Union") or str_rep.startswith("typing.Optional"):
- annos = [unpact_annotation(x, data_dict, address_dict) for x in obj.__args__]
- out = " | ".join(_deduplicate_list(annos))
- # This is a Dict, List, Tuple, etc. Just unpack.
- elif hasattr(obj, "__args__"):
- name = str_rep.replace("typing.", "").split("[")[0]
- sub = [unpact_annotation(x, data_dict, address_dict) for x in obj.__args__]
- out = f"{name}[{', '.join(sub)}]"
- # this is a typevar
- elif hasattr(obj, "__bound__"):
- out = unpact_annotation(obj.__bound__, data_dict, address_dict)
- # this is a class in dascore
- elif str_id in data_dict:
- data = data_dict[str_id]
- out = f"[{data['name']}](`{data['key']}`)"
- # this is a typing type, like Any
- elif str_rep.startswith("typing."):
- out = str_rep.split("typing.")[-1]
- # NoneType should just be None
- elif is_it_subclass(obj, type(None)):
- out = "None"
- # ...
- elif obj is Ellipsis:
- out = "..."
- # generic class from another library
- elif str_rep.startswith("", "")
- # return a self type.
- elif str_rep.endswith(".Self"):
- out = "Self"
- # probably a literal, just give up here
- else:
- if isinstance(obj, str): # make str look like str
- out = f"'{obj!s}'"
- else:
- out = str(obj)
- return out.strip()
-
-
-def get_type_hints(obj) -> dict:
- """
- Get an object's type hints, tolerating names missing at runtime.
-
- Annotations only imported under `TYPE_CHECKING` cannot be resolved when
- the docs are built, so fall back to the unevaluated annotations rather
- than failing the entire build.
-
- The fallback is all-or-nothing because `get_type_hints` resolves an
- object's annotations together. Today only class-level annotations hit
- it, and those are never matched against the parameters a signature is
- built from, so nothing renders differently.
- """
- try:
- return typing.get_type_hints(obj)
- except NameError:
- return dict(inspect.get_annotations(obj))
-
-
-def build_signature(data, data_dict, address_dict):
- """Return html of signature block."""
- sentinel = object() # to know missing values
-
- def get_annotation_str(param):
- """Get string of annotation."""
- if not param:
- return ""
- annotation_str = unpact_annotation(param, data_dict, address_dict)
- return f": {annotation_str}"
-
- def get_param_prefix(kind):
- """Get the prefix for a parameter (eg ** in **kwargs)."""
- if kind == inspect.Parameter.VAR_POSITIONAL:
- return "*"
- elif kind == inspect.Parameter.VAR_KEYWORD:
- return "**"
- return ""
-
- def get_default_value(param, sig):
- """Get the default value for a parameter."""
- default = sig.parameters[param].default
- if default is inspect._empty:
- return sentinel
- if default == "":
- default = '""'
- return str(default)
-
- def get_params(sig, annotations):
- out = []
- for param, value in sig.parameters.items():
- prefix = get_param_prefix(value.kind)
- annotation_str = get_annotation_str(annotations.get(param))
- return_str = get_default_value(param, sig)
- param_str = f"{prefix + param}{annotation_str}\n"
- if return_str is not sentinel:
- param_str += f" = {return_str}"
- out.append(param_str)
- return out
-
- def get_return_line(sig):
- annotation = sig.return_annotation
- if annotation is inspect._empty:
- return_str = ")"
- else:
- return_str = unpact_annotation(annotation, data_dict, address_dict)
- return_str = f")-> {return_str}"
- return return_str
-
- def get_sig_dict(data):
- """Create a dict of render-able signature stuff."""
- sig = data["signature"]
- annotations = get_type_hints(data["object"])
- out = dict(
- params=get_params(sig, annotations),
- return_line=get_return_line(sig),
- name=data["name"],
- )
- return out
-
- # no need to do anything if entity is not callable.
- if not data["signature"]:
- return ""
-
- template = get_template("signature.html")
- sig_dict = get_sig_dict(data)
- out = template.render(**sig_dict)
- return out
-
-
-class NumpyDocStrParser:
- """Class which parses/process docstrings written in numpy style."""
-
- heading_char = "##"
-
- def __init__(self, data):
- self._data = data
-
- def parse_sections(self, docstr):
- """Parse the sections of the docstring."""
- out = {}
- doc_lines = docstr.split("\n")
- dividers = (
- [0]
- + [
- num
- for num, x in enumerate(doc_lines)
- if (set(x).issubset({" ", "-", "\t"}) and ("-" in x))
- ]
- + [len(doc_lines) + 2]
- )
- for start, stop in pairwise(dividers):
- # determine header or just use 'pre' for txt before headings
- header = "pre" if start == 0 else doc_lines[start - 1].strip()
- # skips the ----- line where applicable
- lstart = start if header == "pre" else start + 1
- out[header] = "\n".join(doc_lines[lstart : stop - 2]).strip()
- return out
-
- def style_parameters(self, param_str):
- """
- Style the parameters block.
- """
- lines = param_str.split("\n")
- # parameters don't have spaces at the start
- param_start = [num for num, x in enumerate(lines) if not x.startswith(" ")]
- param_start.append(len(lines))
- # parse parameters into (parameter): txt
- param_desc = []
- for ind_num, ind in enumerate(param_start[:-1]):
- key = lines[ind].strip()
- # get the number of indents (usually 4)
- in_char = (len(lines[1]) - len(lines[1].lstrip())) * " "
- desc_lines = lines[ind + 1 : param_start[ind_num + 1]]
- vals = [
- # strip out the first indentation line
- (x[len(in_char) :] if x.startswith(in_char) and in_char else x)
- for x in desc_lines
- ]
- # breakpoint()
- param_desc.append((key, " ".join(vals)))
- table = pd.DataFrame(param_desc, columns=["Parameter", "Description"])
- return build_table(table)
-
- def style_examples(self, example_str):
- """
- Styles example blocks.
-
- Example blocks can be written in standard doc-test or quarto styles.
- """
- return to_quarto_code(example_str)
-
- def style_notes(self, notes_str):
- """Styles notes section of str."""
- template = get_template("notes.md")
- return template.render(note_text=notes_str)
-
- def style_sections(self, raw_sections):
- """Apply styling to sections."""
- out = dict(raw_sections)
- lower2upper = {x.lower(): x for x in out}
- overlap = set(lower2upper) & set(self.stylers)
- for name in overlap:
- upper_name = lower2upper[name]
- out[upper_name] = self.stylers[name](self, out[upper_name])
- return out
-
- def __call__(self):
- """Process the docstring."""
- sections_raw = self.parse_sections(self._data["docstring"])
- sections_styled = self.style_sections(sections_raw)
- out = []
- for name, content in sections_styled.items():
- if name == "pre" or "note" in name.lower():
- out.append(content)
- else:
- out.append(f"\n{self.heading_char} {name}\n{content}")
- return "\n".join(out)
-
- # stylers are used to decide which style function to apply for various
- # sections.
- stylers = { # noqa
- "parameter": style_parameters,
- "parameters": style_parameters,
- # Numpydoc's section for arguments a function holds and forwards
- # rather than reads; they are parameters and render as one.
- "other parameters": style_parameters,
- "attributes": style_parameters,
- "examples": style_examples,
- "example": style_examples,
- "notes": style_notes,
- "note": style_notes,
- }
-
-
-def to_quarto_code(code_lines):
- """Class for parsing code (eg examples in docstrings) to quarto output."""
- option_chars = "#|"
-
- def _get_blocks(code_lines):
- """Get code blocks. Code blocks are divided by titles "###"."""
- code_blocks = defaultdict(list)
- current_key = ""
- for line in code_lines:
- if line.startswith("###"):
- current_key = line.replace("###", "").lstrip()
- else:
- code_blocks[current_key].append(line)
- return code_blocks
-
- def _is_doctest_line(line):
- strip = line.lstrip()
- doc_str_line = strip.startswith(">>>") or strip.startswith("...")
- return doc_str_line
-
- def _strip_code(code_str):
- """Strip off spaces and the doctest stuff (..., >>>, etc)."""
- out = []
- code_lines = code_str.splitlines()
- # first determine if this is a docstring or not
- is_docstring = False
- for line in code_lines:
- strip = line.lstrip()
- if strip.startswith(">>>"):
- is_docstring = True
- # first determine how much to strip from each line.
- start_index = 999
- for line in code_lines:
- striped = line.lstrip().lstrip(">").lstrip(".").lstrip()
- if not len(striped):
- continue
- if is_docstring and not _is_doctest_line(line):
- continue
- strip_len = len(line) - len(striped)
- start_index = min([start_index, strip_len])
- # then do the stripping
- for line in code_lines:
- if (is_docstring and _is_doctest_line(line)) or not is_docstring:
- out.append(line[start_index:])
- return out
-
- def _get_options(code_lines):
- """Get all quarto options used."""
- options = []
- code_segments = []
- # first get min space to strip.
- for line in code_lines:
- if line.startswith(option_chars):
- options.append(line)
- # Strip out the python code specifier.
- elif line.startswith("```{"):
- continue
- else:
- code_segments.append(line)
- return code_segments, options
-
- def _strip_blocks(code_blocks):
- """Strip empty lines from start and end of list of codes."""
- while len(code_blocks) and not code_blocks[0].strip():
- code_blocks = code_blocks[1:]
- while len(code_blocks) and not code_blocks[-1].strip():
- code_blocks = code_blocks[:-1]
- return code_blocks
-
- def _to_quarto(options, blocks):
- """Convert output to quarto string."""
- out = []
- for section_name, code_blocks in blocks.items():
- title = [] if not section_name else [f"### {section_name}"]
- stripped_block = _strip_blocks(code_blocks)
- if not stripped_block:
- continue
- new_code = [*title, "```{python}", *options, *stripped_block, "```"]
- out.append("\n".join(new_code).lstrip())
- return "\n".join(out)
-
- code_lines_stripped = _strip_code(code_lines)
- code_lines_no_options, options = _get_options(code_lines_stripped)
- blocks = _get_blocks(code_lines_no_options)
- out = _to_quarto(options, blocks)
- return out
-
-
-class Render:
- """A class to render API docs for each entity."""
-
- # The order in which to render the tables.
- _table_order = ("parameter", "method", "attribute", "function", "class", "module")
-
- def __init__(self, data_dict, object_id, address_dict):
- self._data_dict = data_dict
- self._data = data_dict[object_id]
- self._address_dict = address_dict
-
- def has_subsection(self, name):
- """Return True if a module has a subsection."""
- possible_children = self._data.get(name, {})
- children = self.get_children_object_ids(possible_children)
- return bool(children)
-
- def render_linked_table(self, name):
- """Render a table for a specific name."""
- out = []
- for oid in self._data[name]:
- if oid not in self._data_dict:
- continue
- sub_data = self._data_dict[oid]
- linked_name = f"[{sub_data['name']}](`{sub_data['key']}`)"
- desc = sub_data["short_description"]
- out.append((linked_name, desc))
- df = pd.DataFrame(out, columns=["name", "description"])
- return build_table(df)
-
- def get_children_object_ids(self, ids):
- """Of an object id list, return the children of the current object."""
- key = self._data["key"]
- out = {
- x for x in ids if x in self._data_dict and key in self._data_dict[x]["key"]
- }
- return out
-
- def _get_github_source(self, data):
- """Get the github source url."""
- obj = data["object"]
- source_code, line_start = inspect.getsourcelines(obj)
- line_end = line_start + len(source_code)
- rel_path = data["path"].relative_to(data["base_path"])
- # grab repo stuff from environment (on GH actions) or defaults
- github_url = os.environ.get("GITHUB_SERVER_URL", GITHUB_PATH)
- owner_repo = os.environ.get("GITHUB_REPOSITORY", GITHUB_REPOSITORY)
- branch = os.environ.get("GITHUB_REF", GITHUB_REF).split("/")[-1]
- source_url = (
- f"{github_url}/{owner_repo}/blob/{branch}/{rel_path!s}"
- f"#L{line_start + 1}-L{line_end}"
- )
- return source_url
-
- def _get_class_parent_string(self, data):
- """Get the class parent string."""
- obj = data["object"]
- parents = [x for x in obj.__mro__[1:-1]]
- # this class only inherits from object (or type if metaclass)
- if not len(parents):
- return ""
- parent_list = []
- for parent in parents:
- name = parent.__name__
- key = str(id(parent))
- if parent_data := self._data_dict.get(key):
- parent_str = f"[{name}](`{parent_data['key']}`)"
- else:
- parent_str = (
- str(parent).replace("", "").replace("'", "")
- )
- parent_list.append(parent_str)
- out = f" inherits from: {', '.join(parent_list)} \n"
- return out
-
- def _get_parent_source_block(self, data):
- """Create a parent block with a link."""
- parent = data["key"].removesuffix(f".{data['name']}")
- if parent:
- parent_str = f" of [{parent}](`{parent}`)"
- else:
- parent_str = ""
- origin_txt = f"*{self._data['data_type']}* {parent_str}"
- source_url = self._get_github_source(data)
-
- if inspect.isclass(data["object"]):
- # add class's ancestor(s)
- origin_txt += self._get_class_parent_string(data)
-
- template = get_template("parent_source_block.html")
- out = template.render(origin_txt=origin_txt, source_url=source_url)
- return out
-
- def render_markdown(self, heading="##"):
- """Convert numpy docstrings to markdown with some html styling."""
- data = self._data
- docstr = NumpyDocStrParser(data)
-
- tables = [
- f"\n{heading} {_simple_plural(x).capitalize()}\n"
- f"{self.render_linked_table(x)}\n"
- for x in self._table_order
- if self.has_subsection(x)
- ]
- signature = build_signature(data, self._data_dict, self._address_dict)
- out = (
- f"# {self._data['name']}\n\n"
- f"{self._get_parent_source_block(data)}\n\n"
- f"{signature}\n"
- f"{docstr()}\n"
- f"{''.join(tables)}\n"
- )
- return out
-
-
-def create_json_mapping(data_dict, obj_dict, api_path):
- """Create the mapping which links address to file path."""
- out = {}
-
- # add code paths
- for obj_id, data in data_dict.items():
- sub_dir = api_path / "/".join(data["key"].split(".")[:-1])
- path = api_path / sub_dir / f"{data['name']}.qmd"
- out[data["key"]] = f"/{path.relative_to(DOC_PATH)}"
-
- # add alias (eg import shortcuts/imports from other modules)
- for alias in set(obj_dict) - set(out):
- obj_id = obj_dict[alias]
- if obj_id not in data_dict:
- continue
- main_key = data_dict[obj_id]["key"]
- out[alias] = out[main_key]
- return out
-
-
-def write_api_markdown(data_dict, api_path, address_dict, debug=False):
- """Write all the markdown to disk."""
- files_to_delete = set(Path(api_path).rglob("*.qmd"))
- for obj_id, data in data_dict.items():
- # get path and ensure parents exist
- sub_dir = api_path / "/".join(data["key"].split(".")[:-1])
- path = api_path / sub_dir / f"{data['name']}.qmd"
-
- path.parent.mkdir(exist_ok=True, parents=True)
- # remove path from files to delete
- if path in files_to_delete:
- files_to_delete.remove(path)
- # don't render non-target file if debugging
- if debug and data["name"] != "read":
- continue
- # render and write
- render = Render(data_dict, obj_id, address_dict)
- markdown = render.render_markdown()
- # check if file has changed, if not don't write
- if path.exists() and sha_256(path) == sha_256(markdown):
- continue
- path.write_text(markdown)
- # remove files that are no longer written. This can happen when the code is
- # refactored or objects are deleted.
- for path in files_to_delete:
- path.unlink()
-
-
-def _clear_empty_directories(parent_path):
- """Recursively delete empty directories."""
-
- def _dir_empty(path):
- """Return True if directory is empty."""
- contents = (
- x
- for x in path.rglob("*")
- if not (x.name.startswith(".") and len(x.name) < 3)
- )
- if any(contents):
- return False
- return True
-
- for path in parent_path.rglob("*"):
- if not path.is_dir():
- continue
- if _dir_empty(path):
- os.rmdir(path)
-
-
-def _map_other_qmd_files(doc_path=DOC_PATH, api_path=API_DOC_PATH):
- """Add all other qmd files, excluding API."""
- out = {}
- parent_doc = doc_path.parent
- api_relative = str(api_path.relative_to(doc_path))
- for path in DOC_PATH.rglob("*.qmd"):
- path_relative = str(path.relative_to(parent_doc))
- # Skip API docs
- if path_relative.startswith(api_relative):
- continue
- value = "/" + str(path.relative_to(doc_path))
- out[path_relative] = value
- # also add key with no qmd extension.
- out[path_relative.split(".")[0]] = value
- return out
-
-
-def render_project(data_dict, address_dict, api_path=API_DOC_PATH, debug=False):
- """Render the markdown files."""
- # Create and write the qmd files for each function/class/module
- write_api_markdown(data_dict, api_path, address_dict, debug=debug)
- # put the parts together; alias; path to docs
- path_mapping = create_json_mapping(data_dict, address_dict, api_path)
- # Add the other parts of the documentation.
- path_mapping.update(_map_other_qmd_files())
- # dump the json mapping to disk in doc folder
- cross_ref_path = Path(DOC_PATH) / ".cross_ref.json"
- with open(cross_ref_path, "w") as fi:
- json.dump(path_mapping, fi, indent=2)
- # Clear out empty directories
- _clear_empty_directories(api_path)
diff --git a/scripts/_templates/_quarto.yml b/scripts/_templates/_quarto.yml
deleted file mode 100644
index fdf8ca1ef..000000000
--- a/scripts/_templates/_quarto.yml
+++ /dev/null
@@ -1,240 +0,0 @@
-# NOTE: This file is auto generated by a template in the scripts directory
-# dascore/scripts/templates/_quarto.yml, be sure to change that file.
-
-project:
- type: website
- output-dir: _site
-
-filters:
- - quarto
- - filters/fill_links.py
-
-execute:
- warning: false
-
-format:
- html:
- toc: true
- theme:
- light: yeti
- dark: darkly
- code-copy: true
- code-overflow: wrap
- css: styles.css
-
-website:
- title: {{ dascore_version_str }}
- repo-url: https://github.com/dasdae/dascore
- site-path: /docs
- site-url: https://www.dascore.org
-
- image: "_static/logo.png"
- favicon: "_static/logo.png"
- repo-subdir: docs
- repo-branch: master
- repo-actions: [edit]
- page-navigation: true
-
- navbar:
- logo: _static/logo.png
- logo-alt: "DASCore logo."
- background: light
- search: true
- left:
- - text: Introduction
- file: index.qmd
-
- - text: Tutorial
- file: tutorial/concepts.qmd
-
- - text: Recipes
- file: recipes/overview.qmd
-
- - text: Notes
- file: notes/notes.qmd
-
- - text: Contributing
- file: contributing/contributing.qmd
-
- - text: API
- file: api/dascore.qmd
-
- right:
-
- - icon: github
- href: https://github.com/dasdae/dascore
- aria-label: DASCore GitHub
-
- sidebar:
- - id: about
- title: "About"
- style: "floating"
- collapse-level: 2
- logo: "_static/logo.png"
- search: true
- contents:
- - text: Introduction
- href: index.qmd
-
- - text: Formats
- href: supported_formats.qmd
-
- - text: Plugins
- href: supported_plugins.qmd
-
- - text: Changelog
- href: https://github.com/DASDAE/dascore/releases
-
- - text: Contributors
- href: contributors.qmd
-
- - text: Acknowledgements
- href: acknowledgements.qmd
-
- - id: tutorial
- title: "Tutorial"
- contents:
- - text: DASCore Concepts
- href: tutorial/concepts.qmd
-
- - text: Patch
- href: tutorial/patch.qmd
-
- - text: Spool
- href: tutorial/spool.qmd
-
- - text: Processing
- href: tutorial/processing.qmd
-
- - text: Transformations
- href: tutorial/transformations.qmd
-
- - text: Visualization
- href: tutorial/visualization.qmd
-
- - text: File IO
- href: tutorial/file_io.qmd
-
- - text: Working with Remote Patches
- href: tutorial/remote_patches.qmd
-
- - text: Configuration
- href: tutorial/configuration.qmd
-
- - text: Coordinates
- href: tutorial/coords.qmd
-
- - id: Recipes
- title: 'Recipes'
- collapse-level: 1
- contents:
- - recipes/overview.qmd
-
- - section: "Contributing"
- contents:
-
- - recipes/how_to_contribute.qmd
- - recipes/contributing_to_documentation.qmd
-
- - section: "Coordinates"
- contents:
-
- - recipes/add_spatial_coordinates_to_patch.qmd
- - recipes/plotting_channel_number.qmd
-
- - section: "Processing"
- contents:
-
- - recipes/despiking.qmd
- - recipes/smoothing.qmd
- - recipes/correlate.qmd
- - recipes/edge_effects.qmd
- - recipes/fk.qmd
- - recipes/real_time_proc.qmd
- - recipes/parallelization.qmd
- - recipes/low_freq_proc.qmd
-
- - section: "IO"
- contents:
-
- - recipes/external_conversion.qmd
-
- - section: "Misc"
- contents:
-
- - recipes/docker_basic.qmd
-
- - id: contributing
- title: "Contributing"
- contents:
- - contributing/contributing.qmd
-
- - text: Dev Install
- href: contributing/dev_install.qmd
-
- - text: Style
- href: contributing/style_and_linting.qmd
-
- - text: "Testing"
- href: contributing/testing.qmd
-
- - text: "Adding Test Data"
- href: contributing/adding_test_data.qmd
-
- - text: Documentation
- href: contributing/documentation.qmd
-
- - text: Profiling and Benchmarks
- href: contributing/profiling_benchmarks.qmd
-
- - text: General Guidelines
- href: contributing/general_guidelines.qmd
-
- - text: "Adding a new format"
- href: contributing/new_format.qmd
-
- - text: "Extending DASCore"
- href: contributing/extending_dascore.qmd
-
- - text: "Publish a new release"
- href: contributing/publish_a_new_release.qmd
-
- - text: "Code of Conduct"
- href: contributing/code_of_conduct.qmd
-
- - id: Notes
- title: 'Notes'
- href: notes/notes.qmd
- contents:
- - notes/notes.qmd
-
- - text: Documentation Strategy
- href: notes/doc_strategy.qmd
-
- - text: Coordinate Internals
- href: notes/coordinate_internals.qmd
-
- - text: PatchAttrs
- href: notes/patch_attrs.qmd
-
- - text: Fourier Transforms
- href: notes/dft_notes.qmd
-
- - text: Velocity to Strain Rate
- href: notes/velocity_to_strain_rate.qmd
-
- - text: Spool Index
- href: notes/spool_index.qmd
-
- - text: Spool Selection
- href: notes/spool_selection.qmd
-
- - id: API
- title: "API"
- href: api/dascore.qmd
- contents:
- {% for item in api_toc_tree -%}
- {{ item }}
- {% endfor %}
-
-bibliography: references.bib
diff --git a/scripts/_templates/notes.md b/scripts/_templates/notes.md
deleted file mode 100644
index 6737be8a8..000000000
--- a/scripts/_templates/notes.md
+++ /dev/null
@@ -1,4 +0,0 @@
-
-:::{.callout-note}
-{{ note_text }}
-:::
diff --git a/scripts/_templates/parent_source_block.html b/scripts/_templates/parent_source_block.html
deleted file mode 100644
index 5e5da1ee1..000000000
--- a/scripts/_templates/parent_source_block.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
{{ origin_txt }}
-
[source]({{ source_url }})
-
-
diff --git a/scripts/_templates/signature.html b/scripts/_templates/signature.html
deleted file mode 100644
index cc672ef29..000000000
--- a/scripts/_templates/signature.html
+++ /dev/null
@@ -1,19 +0,0 @@
-:::{.padded_bottom_10pt}
-
-:::{.def_block}
-{% if params %}
-
-{{ name }}(
-{%- for param in params %}
- {{ param }},
-{%- endfor %}
-{{ return_line }}
-
-{% else %}
-
-{{ name }}({{ return_line }}
-
-{% endif %}
-:::
-
-:::
diff --git a/scripts/_templates/table.html b/scripts/_templates/table.html
deleted file mode 100644
index 555a3a3e6..000000000
--- a/scripts/_templates/table.html
+++ /dev/null
@@ -1,24 +0,0 @@
-{# A template for making tables. #}
-
- {%- if caption %}
-
- {{ caption }}
-
- {%- endif %}
-
-
- {%- for col in columns %}
-
{{ col }}
- {%- endfor %}
-
-
-
- {%- for row in rows %}
-
- {%- for val in row %}
-
{{ val }}
- {%- endfor %}
-
- {%- endfor %}
-
-
diff --git a/scripts/_validate_links.py b/scripts/_validate_links.py
deleted file mode 100644
index dc2309444..000000000
--- a/scripts/_validate_links.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""Script to validate links in qmd files."""
-
-from __future__ import annotations
-
-import json
-import re
-from functools import cache
-from pathlib import Path
-
-
-def _get_docs_path():
- """Find the documentation path."""
- path = Path(__file__).parent.parent / "docs"
- return path
-
-
-def get_qmd_files(
- path=None,
-):
- """Yield all QMD files."""
- path = _get_docs_path() if path is None else path
- yield from path.rglob("*qmd")
-
-
-def yield_links(text, pattern=r"(?<=\]\(`).*?(?=`\))"):
- """Yield links found in documentation."""
- matches = re.findall(pattern, text)
- yield from matches
-
-
-@cache
-def load_index(path=None):
- """Load the index with the linked locations."""
- if path is None:
- path = _get_docs_path() / ".cross_ref.json"
- with open(path) as fi:
- out = json.load(fi)
- return out
-
-
-def validate_all_links():
- """Scan all documentation files and ensure the links are valid."""
- index = load_index()
- good_links, bad_links, file_count = 0, 0, 0
- bad = []
- for path in get_qmd_files():
- file_count += 1
- text = path.read_text()
- for link in yield_links(text):
- if link not in index:
- bad.append((str(path), link))
- bad_links += 1
- else:
- good_links += 1
- print( # noqa
- f"Validated links in documentation. Scanned {file_count} files, "
- f"found {good_links} good links and {bad_links} bad links"
- )
- if bad_links:
- msg = "Please fix the following (path/link)\n"
- max_len = max(len(x[0]) for x in bad)
- out = []
- for path, link in bad:
- path_str = path.ljust(max_len + 3)
- out.append(f"{path_str} {link}")
- new_str = msg + "\n".join(out)
- raise ValueError(new_str)
-
-
-if __name__ == "__main__":
- validate_all_links()
- pass
diff --git a/scripts/build_api_docs.py b/scripts/build_api_docs.py
deleted file mode 100644
index 0c84811f3..000000000
--- a/scripts/build_api_docs.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""Script to build the API docs for dascore."""
-
-from __future__ import annotations
-
-import sys
-from contextlib import suppress
-
-from _index_api import get_alias_mapping, parse_project
-from _qmd_builder import create_quarto_qmd
-from _render_api import render_project
-from _validate_links import validate_all_links
-
-import dascore as dc
-
-with suppress(AttributeError):
- sys.stdout.encoding = "utf-8"
-
-
-if __name__ == "__main__":
- print("Building documentation") # noqa
- print(f"Parsing project {dc.__name__}") # noqa
- data_dict = parse_project(dc)
- obj_dict = get_alias_mapping(dc)
- print("Generating qmd files") # noqa
- render_project(data_dict, obj_dict, debug=False)
- # create the quarto info file (needs templating)
- print("creating quarto config") # noqa
- create_quarto_qmd()
- # validate links
- print("Validating links") # noqa
- validate_all_links()
diff --git a/scripts/generate_doc_code_tests.py b/scripts/generate_doc_code_tests.py
index 665b8e674..1f3021558 100644
--- a/scripts/generate_doc_code_tests.py
+++ b/scripts/generate_doc_code_tests.py
@@ -6,8 +6,8 @@
How it works:
-1. Discover every source ``.qmd`` file under ``docs/``, excluding generated API
- docs under ``docs/api/``.
+1. Discover every source ``.qmd`` file in the narrative doc sections
+ (``tutorial/``, ``recipes/``, ...) plus the landing page.
2. Parse each qmd file and keep only executable Quarto ``{python ...}`` fences.
3. Respect document-level ``execute.eval: false`` and chunk-level
``#| eval: false`` / ``#| execute: false`` switches.
@@ -38,12 +38,15 @@
# The generator always runs from the repository checkout.
REPO_ROOT = Path(__file__).resolve().parent.parent
-# Source qmd files live under docs/.
-DOCS_PATH = REPO_ROOT / "docs"
+# Narrative doc sources sit at the top of the repo, one directory per
+# great-docs section, so paths mirror the site layout.
+DOCS_PATH = REPO_ROOT
+# Keep in sync with the `sections:` entries in great-docs.yml.
+DOC_SECTIONS = ("tutorial", "recipes", "notes", "contributing", "about")
+# The landing page is the one doc source outside a section directory.
+ROOT_DOCS = ("index.qmd",)
# Generated pytest files are mirrored into a dedicated test tree.
TESTS_PATH = REPO_ROOT / "tests" / "test_autogenerated_doccode"
-# API docs are generated elsewhere and should not be mirrored again here.
-API_DOCS_PATH = DOCS_PATH / "api"
# Force stable cross-platform text IO for both reads and writes.
TEXT_ENCODING = "utf-8"
@@ -307,12 +310,12 @@ def extract_qmd_file(path: Path) -> QmdFile:
def iter_source_qmd_files(base_path: Path = DOCS_PATH) -> list[Path]:
"""Return source qmd files to mirror into tests."""
- # Mirror every qmd under docs/, except generated API docs.
- return [
- path
- for path in sorted(base_path.rglob("*.qmd"))
- if API_DOCS_PATH not in path.parents and path != API_DOCS_PATH
- ]
+ # Only the narrative sections are walked; everything else at the repo root
+ # (generated sites, test fixtures, ...) is not documentation source.
+ paths = [base_path / name for name in ROOT_DOCS if (base_path / name).exists()]
+ for section in DOC_SECTIONS:
+ paths.extend((base_path / section).rglob("*.qmd"))
+ return sorted(paths)
def get_output_path(
@@ -321,8 +324,8 @@ def get_output_path(
docs_path: Path = DOCS_PATH,
) -> Path:
"""Map a qmd file to its generated pytest module."""
- # Keep the generated tree shaped like docs/, but swap the filename to
- # `test_.py` so pytest discovers it naturally.
+ # Keep the generated tree shaped like the doc sections, but swap the
+ # filename to `test_.py` so pytest discovers it naturally.
relative = source_path.relative_to(docs_path)
filename = f"test_{source_path.stem}.py"
if len(relative.parts) == 1:
diff --git a/scripts/greatdocs_alias_inventory.py b/scripts/greatdocs_alias_inventory.py
new file mode 100644
index 000000000..3d32ad20f
--- /dev/null
+++ b/scripts/greatdocs_alias_inventory.py
@@ -0,0 +1,248 @@
+"""
+Quarto pre-render hook: add alias entries to the great-docs link inventory.
+
+DASCore exposes most functionality through aliases: processing functions are
+attached to Patch as methods (``Patch.taper = dascore.proc.taper``), objects
+are re-exported at several import paths, and narrative docs link to modules.
+great-docs only indexes the canonical location of each documented object, so
+links written against an alias path (e.g. ``[taper](`dascore.Patch.taper`)``)
+would not resolve.
+
+This script runs inside the great-docs build directory (wired via the
+``pre_render`` key of great-docs.yml) after the API reference and
+``objects.json`` are generated but before Quarto renders the site. It imports
+dascore, resolves every inventory entry to its runtime object, and then adds
+an inventory alias for every public access path that reaches a documented
+object. Module links are pointed at their section of the API index page.
+
+The heavy lifting (link resolution in rendered HTML) stays in great-docs;
+this only feeds it a richer inventory.
+"""
+
+from __future__ import annotations
+
+import inspect
+import json
+import os
+import sys
+from pathlib import Path
+
+OBJECTS_JSON = Path("objects.json")
+
+# Where the single-page utils reference lands in the built site.
+#
+# great-docs resolves inventory uris in two ways: with the linking page's path
+# (a relative path is computed, which normalizes the `..`) and, on reference
+# pages, by simply stripping a leading `reference/` -- that branch assumes
+# every documented object lives under `reference/`. Spelling the path this way
+# lands on `../utilities/index.html` either way. Worth reporting upstream.
+UTILITIES_PAGE = "reference/../utilities/index.html"
+
+# Modules whose links should land on a section of the API index.
+MODULE_SECTIONS = {
+ "dascore.core": "reference/index.html#core-classes",
+ "dascore.examples": "reference/index.html#example-data",
+ "dascore.exceptions": "reference/index.html#exceptions",
+ "dascore.io": "reference/index.html#io-interfaces",
+ "dascore.proc": "reference/index.html#patch-processing",
+ "dascore.proc.aggregate": "reference/index.html#patch-processing",
+ "dascore.transform": "reference/index.html#transforms",
+ "dascore.units": "reference/index.html#units-and-time",
+ "dascore.viz": "reference/index.html#visualization",
+ "dascore.constants": "reference/index.html",
+}
+
+
+def _ensure_dascore():
+ """Import dascore, re-execing with the project venv python if needed.
+
+ Quarto runs pre-render scripts with whatever python it finds first, which
+ is not necessarily the environment dascore is installed in.
+ """
+ try:
+ import dascore # noqa: F401
+
+ return
+ except ImportError:
+ pass
+ project_dir = Path(os.environ.get("QUARTO_PROJECT_DIR", "."))
+ for candidate in (
+ project_dir.parent / ".venv" / "bin" / "python",
+ project_dir.parent / ".venv" / "Scripts" / "python.exe",
+ ):
+ if candidate.is_file() and str(candidate) != sys.executable:
+ os.execv(str(candidate), [str(candidate), *sys.argv])
+ raise SystemExit(
+ "greatdocs_alias_inventory: could not import dascore; install it in "
+ "the python environment quarto uses for pre-render scripts."
+ )
+
+
+def _unwrap(obj):
+ seen = set()
+ while getattr(obj, "__wrapped__", None) is not None and id(obj) not in seen:
+ seen.add(id(obj))
+ obj = obj.__wrapped__
+ return obj
+
+
+def _resolve(name: str):
+ """Resolve a dotted inventory name to a runtime object, or None."""
+ import dascore
+
+ parts = name.split(".")
+ if parts[0] != "dascore":
+ return None
+ obj = dascore
+ for part in parts[1:]:
+ try:
+ obj = getattr(obj, part)
+ except AttributeError:
+ return None
+ return obj
+
+
+def main() -> None:
+ """Append alias entries to the interlink inventory."""
+ _ensure_dascore()
+ import dascore as dc
+
+ data = json.loads(OBJECTS_JSON.read_text())
+ items = data["items"]
+ known = {it["name"] for it in items}
+
+ # 0. Point uris at anchors the rendered pages actually emit. great-docs
+ # records `Patch.html#dascore.Patch.data`, but quarto gives the member
+ # section the id pandoc derives from its heading: the short name,
+ # lowercased (`Patch.T` lands at `#t`). An object which owns its whole
+ # page (write.html), and a private member the page does not render at
+ # all, are honestly linked at the top of the page instead. This runs
+ # first so the aliases added below copy the corrected uris.
+ rewritten = 0
+ for item in items:
+ uri = item.get("uri", "")
+ page, sep, fragment = uri.partition("#")
+ if not sep or not page.startswith("reference/"):
+ continue
+ page_object = page[len("reference/") : -len(".html")]
+ qualname = fragment.removeprefix("dascore.")
+ member = qualname.split(".")[-1]
+ if qualname == page_object or member.startswith("_"):
+ new_uri = page
+ else:
+ new_uri = f"{page}#{member.lower()}"
+ if new_uri != uri:
+ item["uri"] = new_uri
+ rewritten += 1
+
+ # Map runtime object id -> best inventory item (prefer non-module roles,
+ # e.g. proc.taper the function page over proc.taper the module).
+ by_id: dict[int, dict] = {}
+ for it in items:
+ obj = _resolve(it["name"])
+ if obj is None:
+ continue
+ current = by_id.get(id(_unwrap(obj)))
+ if current is None or (current["role"] == "module" and it["role"] != "module"):
+ by_id[id(_unwrap(obj))] = it
+
+ new_items = []
+
+ def add_alias(name: str, target: dict) -> None:
+ if name in known:
+ return
+ known.add(name)
+ new_items.append(
+ {
+ "name": name,
+ "domain": "py",
+ "role": target["role"],
+ "priority": "2",
+ "uri": target["uri"],
+ "dispname": target.get("dispname", "-"),
+ }
+ )
+
+ # 1. Canonical module.qualname alias for every documented object (e.g.
+ # dascore.utils.patch.get_patch_names for BaseSpool.get_patch_names).
+ for target in by_id.values():
+ obj = _resolve(target["name"])
+ module = getattr(obj, "__module__", None)
+ qualname = getattr(obj, "__qualname__", None)
+ if module and qualname and "<" not in qualname:
+ add_alias(f"{module}.{qualname}", target)
+
+ # 2. Aliases for public attributes of the documented core classes
+ # (covers the dynamically attached Patch/Spool methods).
+ from dascore.core.coords import BaseCoord
+
+ for cls in (dc.Patch, dc.BaseSpool, dc.PatchAttrs, dc.CoordManager, BaseCoord):
+ prefixes = [f"dascore.{cls.__name__}", f"{cls.__module__}.{cls.__qualname__}"]
+ import dascore.core
+
+ if getattr(dascore.core, cls.__name__, None) is cls:
+ prefixes.append(f"dascore.core.{cls.__name__}")
+ for attr in dir(cls):
+ if attr.startswith("_"):
+ continue
+ try:
+ target = by_id.get(id(_unwrap(getattr(cls, attr))))
+ except Exception:
+ continue
+ if target is None:
+ continue
+ for prefix in prefixes:
+ add_alias(f"{prefix}.{attr}", target)
+
+ # 3. Aliases for public re-exports one level below dascore and
+ # dascore.core (e.g. dascore.core.Patch, dascore.io.write).
+ import dascore.core
+
+ for module in (dc, dc.core):
+ for attr in dir(module):
+ if attr.startswith("_"):
+ continue
+ try:
+ target = by_id.get(id(_unwrap(getattr(module, attr))))
+ except Exception:
+ continue
+ if target is not None:
+ add_alias(f"{module.__name__}.{attr}", target)
+
+ # 4. Module links -> API index sections.
+ for name, uri in MODULE_SECTIONS.items():
+ add_alias(name, {"role": "module", "uri": uri, "dispname": "-"})
+
+ # 5. The utilities page documents every helper in dascore.utils on one
+ # page, so point cross references at the anchor of each entry rather
+ # than a page that does not exist.
+ from dascore.utils.docs import get_doc_anchor, iter_package_modules, iter_public
+
+ add_alias("dascore.utils", {"role": "module", "uri": UTILITIES_PAGE})
+ for module_name in iter_package_modules("dascore.utils"):
+ try:
+ members = list(iter_public(module_name))
+ except ImportError:
+ continue
+ if not members:
+ continue
+ page = f"{UTILITIES_PAGE}#{get_doc_anchor(module_name)}"
+ add_alias(module_name, {"role": "module", "uri": page, "dispname": "-"})
+ for name, obj in members:
+ dotted = f"{module_name}.{name}"
+ uri = f"{UTILITIES_PAGE}#{get_doc_anchor(dotted)}"
+ role = "class" if inspect.isclass(obj) else "function"
+ add_alias(dotted, {"role": role, "uri": uri, "dispname": "-"})
+
+ if new_items or rewritten:
+ data["items"] = items + new_items
+ data["count"] = len(data["items"])
+ OBJECTS_JSON.write_text(json.dumps(data, indent=1))
+ print(
+ f"greatdocs_alias_inventory: added {len(new_items)} alias entries, "
+ f"repointed {rewritten} anchors"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/greatdocs_build_fixups.py b/scripts/greatdocs_build_fixups.py
new file mode 100644
index 000000000..306d392fe
--- /dev/null
+++ b/scripts/greatdocs_build_fixups.py
@@ -0,0 +1,181 @@
+"""
+Quarto pre-render hook: patch great-docs-generated pages before rendering.
+
+great-docs generates two pages whose content needs small post-processing that
+the tool does not (yet) do itself. This script runs inside the great-docs build
+directory (wired via the ``pre_render`` key of great-docs.yml), after the pages
+are generated but before Quarto renders them.
+
+1. ``index.qmd`` — great-docs builds the landing page from the repo readme and
+ bumps every ``#`` heading one level (``#`` -> ``##``) so the page nests under
+ its title. That regex also rewrites ``#`` comment lines *inside* fenced code
+ cells, so ``# import ...`` renders as ``## import ...``. We undo the bump for
+ ``##``-or-deeper lines that sit inside code fences.
+
+2. ``changelog.qmd`` — each release section already gets a
+ ``## {version} {.changelog-version}`` heading from great-docs, but the GitHub
+ release body usually *also* starts with a ``# {version}`` heading. That
+ duplicate renders as a stray paragraph or a repeated heading. We drop the
+ leading body heading when its text matches the section's version.
+
+Both fixes are written to be safe to run anywhere: the index fix only touches
+``##``-or-deeper lines (repo-source comments are single ``#`` and are left
+alone), and missing files are skipped.
+"""
+
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+FENCE_RE = re.compile(r"^\s*(`{3,}|~{3,})")
+# A code-comment line that great-docs over-bumped (two or more leading #).
+BUMPED_COMMENT_RE = re.compile(r"^(#{2,})(\s)")
+HEADING_RE = re.compile(r"^\s*(#{1,6})\s+(.*?)\s*$")
+CHANGELOG_VERSION_RE = re.compile(r"^#{1,6}\s+(.*?)\s*\{\.changelog-version\}\s*$")
+DATE_LINE_RE = re.compile(r"^\s*\*.*\[GitHub\]")
+
+
+def fix_index(path: Path) -> int:
+ """De-bump ``#`` comment lines that great-docs doubled inside code fences."""
+ if not path.exists():
+ return 0
+ lines = path.read_text(encoding="utf-8").split("\n")
+ in_fence = False
+ marker = ""
+ changed = 0
+ for i, line in enumerate(lines):
+ m = FENCE_RE.match(line)
+ if m:
+ mk = m.group(1)[0]
+ if not in_fence:
+ in_fence, marker = True, mk
+ elif mk == marker:
+ in_fence, marker = False, ""
+ continue
+ if in_fence:
+ bm = BUMPED_COMMENT_RE.match(line)
+ if bm:
+ lines[i] = line[1:] # drop one leading '#'
+ changed += 1
+ if changed:
+ path.write_text("\n".join(lines), encoding="utf-8")
+ return changed
+
+
+def _fix_release_body(version: str, body: list[str]) -> tuple[list[str], int, int]:
+ """Clean one release body: drop a duplicate version heading and dedent.
+
+ Some GitHub release bodies are uniformly indented (~2 spaces). great-docs'
+ ``.strip()`` un-indents only the *first* line of the body, so the rest stay
+ indented and pandoc renders their ``### ...`` headings as literal text. We
+ split off the date line, drop a leading heading that repeats the version,
+ then strip the body's base indent from every line. The base indent is taken
+ from the note lines *after* the anomalous first line, and we remove at most
+ that many leading spaces per line so relative nesting (e.g. list-item
+ continuations) is preserved.
+ """
+ dropped = deindented = 0
+ # Keep the leading date line (and blanks before it) outside the dedent.
+ head: list[str] = []
+ idx = 0
+ while idx < len(body):
+ head.append(body[idx])
+ if DATE_LINE_RE.match(body[idx]):
+ idx += 1
+ break
+ idx += 1
+ note = body[idx:]
+
+ # Drop a leading heading that just repeats the version.
+ j = 0
+ while j < len(note) and note[j].strip() == "":
+ j += 1
+ if j < len(note):
+ hm = HEADING_RE.match(note[j])
+ if hm and hm.group(2).strip() == version:
+ note = note[:j] + note[j + 1 :]
+ dropped = 1
+
+ # Base indent = the most common leading indent among non-blank lines. Using
+ # the mode (not the min) is robust to the anomalous 0-indent lines these
+ # bodies contain: the generator un-indents the first line, and GitHub's
+ # auto-generated "**Full Changelog**" footer also sits at column 0.
+ indents = [len(ln) - len(ln.lstrip(" ")) for ln in note if ln.strip()]
+ base = 0
+ if indents:
+ counts: dict[int, int] = {}
+ for ind in indents:
+ counts[ind] = counts.get(ind, 0) + 1
+ # Highest count wins; ties resolve to the smaller indent.
+ base = min(counts, key=lambda k: (-counts[k], k))
+ if base:
+ strip_re = re.compile(rf"^ {{1,{base}}}")
+ new_note = [strip_re.sub("", ln) for ln in note]
+ if new_note != note:
+ note = new_note
+ deindented = 1
+ return head + note, dropped, deindented
+
+
+def fix_changelog(path: Path) -> int:
+ """Drop duplicate version headings and dedent indented release bodies."""
+ if not path.exists():
+ return 0
+ lines = path.read_text(encoding="utf-8").split("\n")
+
+ # Find the frontmatter/preamble, then split into per-release blocks at each
+ # `{.changelog-version}` heading.
+ out: list[str] = []
+ version: str | None = None
+ block: list[str] = []
+ dropped = deindented = 0
+
+ def flush() -> None:
+ nonlocal dropped, deindented
+ if version is None:
+ out.extend(block)
+ return
+ fixed, d, di = _fix_release_body(version, block)
+ dropped += d
+ deindented += di
+ out.extend(fixed)
+
+ for line in lines:
+ vm = CHANGELOG_VERSION_RE.match(line)
+ if vm:
+ flush()
+ out.append(line)
+ version = vm.group(1).strip()
+ block = []
+ elif version is None:
+ out.append(line)
+ else:
+ block.append(line)
+ flush()
+
+ if dropped or deindented:
+ # Collapse runs of blank lines left behind to a single blank line.
+ collapsed: list[str] = []
+ for ln in out:
+ if ln.strip() == "" and collapsed and collapsed[-1].strip() == "":
+ continue
+ collapsed.append(ln)
+ path.write_text("\n".join(collapsed), encoding="utf-8")
+ return dropped + deindented
+
+
+def main() -> int:
+ """Patch the generated index and changelog pages in place."""
+ cwd = Path.cwd()
+ n_index = fix_index(cwd / "index.qmd")
+ n_changelog = fix_changelog(cwd / "changelog.qmd")
+ print(
+ f"greatdocs_build_fixups: de-bumped {n_index} code-comment line(s) in "
+ f"index.qmd, dropped {n_changelog} duplicate changelog heading(s)."
+ )
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/test_generate_doc_code_tests.py b/scripts/test_generate_doc_code_tests.py
index c7a772d53..a68c2e989 100644
--- a/scripts/test_generate_doc_code_tests.py
+++ b/scripts/test_generate_doc_code_tests.py
@@ -191,7 +191,7 @@ class TestOutputPaths:
def test_root_doc_maps_to_root_test(self):
"""Top-level docs should stay at the top of generated tests."""
- # docs/index.qmd becomes tests/test_autogenerated_doccode/test_index.py
+ # index.qmd becomes tests/test_autogenerated_doccode/test_index.py
source = DOCS_PATH / "index.qmd"
tests = Path("/repo/tests/test_autogenerated_doccode")
assert get_output_path(source, tests_path=tests) == tests / "test_index.py"
@@ -221,14 +221,14 @@ class TestRenderAndWrite:
def test_render_includes_source_and_chunk_payload(self):
"""Generated modules should inline source code with source comments."""
# The generated file should contain enough literal data to run by itself.
- source = REPO_ROOT / "docs" / "tutorial" / "example.qmd"
+ source = REPO_ROOT / "tutorial" / "example.qmd"
module = render_test_module(source, (Chunk(start_line=12, source="x = 1\n"),))
- assert "Autogenerated from docs/tutorial/example.qmd" in module
+ assert "Autogenerated from tutorial/example.qmd" in module
assert "@pytest.mark.docs_examples" in module
assert "def test_main()" in module
- assert "source_qmd = 'docs/tutorial/example.qmd'" in module
+ assert "source_qmd = 'tutorial/example.qmd'" in module
assert "with qmd_test_context(source_qmd):" in module
- assert "### docs/tutorial/example.qmd:12" in module
+ assert "### tutorial/example.qmd:12" in module
assert "x = 1" in module
assert "CHUNKS =" not in module
assert "SOURCE_QMD =" not in module
@@ -236,7 +236,7 @@ def test_render_includes_source_and_chunk_payload(self):
def test_render_hoists_future_imports(self):
"""Future imports should move to module scope."""
- source = REPO_ROOT / "docs" / "tutorial" / "example.qmd"
+ source = REPO_ROOT / "tutorial" / "example.qmd"
module = render_test_module(
source,
(
@@ -247,7 +247,7 @@ def test_render_hoists_future_imports(self):
),
)
assert "from __future__ import annotations\n\nimport pytest" in module
- assert "# docs/tutorial/example.qmd:12" in module
+ assert "# tutorial/example.qmd:12" in module
assert " x = 1" in module
assert " from __future__ import annotations" not in module
diff --git a/scripts/test_index_api.py b/scripts/test_index_api.py
deleted file mode 100644
index 05d3eb986..000000000
--- a/scripts/test_index_api.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""Tests for the api indexing helpers."""
-
-from __future__ import annotations
-
-from _index_api import _get_base_address, _is_environment_path
-
-
-class TestIsEnvironmentPath:
- """Tests for detecting environment paths nested in the project."""
-
- def test_venv_path(self):
- """A .venv created in the repo should be excluded."""
- assert _is_environment_path("/repo/.venv/lib/python3.12/foo.py")
-
- def test_site_packages_path(self):
- """Any site-packages path should be excluded."""
- assert _is_environment_path("/repo/env/lib/site-packages/scipy/x.py")
-
- def test_project_path(self):
- """Regular project paths should not be excluded."""
- assert not _is_environment_path("/repo/dascore/core/patch.py")
-
-
-class TestGetBaseAddress:
- """Tests for converting paths to addresses."""
-
- def test_environment_path_returns_empty(self):
- """Environment paths should not get a base address."""
- path = "/repo/.venv/lib/python3.12/site-packages/scipy/signal.py"
- assert _get_base_address(path, "/repo") == ""
-
- def test_project_path_returns_address(self):
- """Project paths should convert to dotted addresses."""
- path = "/repo/dascore/core/patch.py"
- assert _get_base_address(path, "/repo") == "dascore.core.patch"
diff --git a/scripts/test_qmd_builder.py b/scripts/test_qmd_builder.py
deleted file mode 100644
index d22933b6e..000000000
--- a/scripts/test_qmd_builder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""Tests for building Quarto config values."""
-
-from __future__ import annotations
-
-import pytest
-
-_qmd_builder = pytest.importorskip("_qmd_builder")
-
-
-class TestGetDascoreTitle:
- """Tests for DASCore title version formatting."""
-
- def test_release_version(self, monkeypatch):
- """Release versions are shown as-is."""
- monkeypatch.delenv("DASCORE_DOC_VERSION", raising=False)
- monkeypatch.setattr(_qmd_builder.dc, "__version__", "0.1.16")
-
- assert _qmd_builder._get_dascore_title() == "DASCore (0.1.16)"
-
- def test_dev_version(self, monkeypatch):
- """Dev versions strip local version metadata."""
- monkeypatch.delenv("DASCORE_DOC_VERSION", raising=False)
- monkeypatch.setattr(_qmd_builder.dc, "__version__", "0.1.16.dev19+gabc123")
-
- assert _qmd_builder._get_dascore_title() == "DASCore (0.1.16.dev19)"
-
- def test_doc_version_override(self, monkeypatch):
- """The docs version override controls the rendered site title."""
- monkeypatch.setenv("DASCORE_DOC_VERSION", "0.1.16")
- monkeypatch.setattr(_qmd_builder.dc, "__version__", "0.1.16.dev19+gabc123")
-
- assert _qmd_builder._get_dascore_title() == "DASCore (0.1.16)"
diff --git a/scripts/test_render_api.py b/scripts/test_render_api.py
deleted file mode 100644
index 29f7156d5..000000000
--- a/scripts/test_render_api.py
+++ /dev/null
@@ -1,134 +0,0 @@
-"""Tests for rendering api stuff."""
-
-from __future__ import annotations
-
-import inspect
-import typing
-
-import pytest
-
-from dascore.core.spool import Spool
-
-# These tests only work if doc deps are installed.
-pytest.importorskip("jinja2")
-
-from _render_api import build_signature, get_type_hints, to_quarto_code
-
-
-class TestGetTypeHints:
- """Tests for resolving the type hints of documented objects."""
-
- def test_resolvable_hints(self):
- """Annotations which resolve should still return their objects."""
-
- def func(a: int) -> str:
- """A documented function."""
-
- hints = get_type_hints(func)
- assert hints["a"] is int
- assert hints["return"] is str
-
- def test_type_checking_only_annotation(self):
- """
- Annotations imported only under TYPE_CHECKING can't be resolved when
- the docs are built, but they shouldn't break the build.
- """
-
- class Klass:
- """A class annotated with a name missing at runtime."""
-
- attr: OnlyImportedWhileTypeChecking # noqa: F821
-
- # The un-guarded call is what used to kill the doc build.
- with pytest.raises(NameError):
- typing.get_type_hints(Klass)
- assert get_type_hints(Klass) == {"attr": "OnlyImportedWhileTypeChecking"}
-
- def test_signature_of_type_checking_annotated_class(self):
- """Spool annotates a TYPE_CHECKING-only import; it must still render."""
- data = {
- "signature": inspect.signature(Spool),
- "object": Spool,
- "name": "Spool",
- }
- out = build_signature(data, {}, {})
- assert "Spool" in out
- assert "data" in out
-
-
-class TestToQuartoCode:
- """Tests for code parsing to quarto-style code strings."""
-
- def test_basic(self):
- """Ensure a simple example works."""
- code = """
- print("hey")
- """
- out = to_quarto_code(code)
- assert '```{python}\nprint("hey")\n```' == out
-
- def test_docstring(self):
- """Ensure docstring works."""
- code = """
- >>> print("bob")
- >>> for a in range(10):
- ... print(a)
- """
- out = to_quarto_code(code)
- assert " print(a)" in out.splitlines()
-
- def test_output_handled(self):
- """Docstrings can have outputs in them, we need to strip them out."""
- code = """
- >>> print("bob")
- bob
- """
- out = to_quarto_code(code)
- assert '```{python}\nprint("bob")\n```' == out
-
- def test_titles(self):
- """Ensure titles are carried forward."""
- code1 = """
- >>> ### Simple example
- >>> print("a")
- >>>
- >>> ### More complex example
- >>> print(1 + 2)
- """
- out1 = to_quarto_code(code1)
- code2 = """
-
- ### Simple example
- print("a")
- ### More complex example
- print(1 + 2)
-
- """
- out2 = to_quarto_code(code2)
- assert out1 == out2
-
- def test_options(self):
- """Ensure quarto options carry forward."""
- code1 = """
- >>> #| fold: true
- >>> print("bob")
- >>>
- >>> ### Another example
- >>> print("bill")
- """
- out = to_quarto_code(code1)
- expected_str = "#| fold: true"
- assert expected_str in out
- assert out.count(expected_str) == 2
-
- def test_combination(self):
- """A combination of stuff."""
- code1 = """
- >>> #| code-fold: true
- >>> # This is a base example
- >>> print(1 + 2)
- >>> ### This is a sub-section
- >>> print("cool beans")
- """
- out = to_quarto_code(code1)
- assert out
diff --git a/skills/dascore/SKILL.md b/skills/dascore/SKILL.md
new file mode 100644
index 000000000..b66af2e34
--- /dev/null
+++ b/skills/dascore/SKILL.md
@@ -0,0 +1,95 @@
+---
+name: dascore
+description: >
+ Python library for distributed fiber optic sensing (DAS/DTS/DSS). Use when
+ reading, processing, transforming, or visualizing fiber-optic sensing data
+ (e.g. HDF5/TDMS/SEGY DAS files from Terra15, Silixa, OptoDAS, Febus, etc.),
+ or when converting such data between formats or to ObsPy/xarray objects.
+license: LGPL-3.0-or-later
+compatibility: Requires Python >=3.10.
+---
+
+# DASCore
+
+DASCore reads, processes, and visualizes distributed acoustic sensing (DAS)
+data. The two central types are:
+
+- **`Patch`** — an n-D array (usually time × distance) with coordinates and
+ metadata. Immutable: every method returns a *new* patch.
+- **Spool** (`dc.spool(...)`) — a collection of patches, backed by memory, a
+ single file, or a directory of files. Iterate it to get patches.
+
+## Install
+
+```bash
+pip install dascore # or: conda install -c conda-forge dascore
+```
+
+## Core workflow
+
+```python
+import dascore as dc
+
+spool = dc.spool("path/to/file_or_directory") # lazy; indexes directories
+spool = spool.select(time=("2023-01-01", ...)) # filter before loading
+spool = spool.chunk(time=60) # re-chunk to 60 s patches
+for patch in spool:
+ out = (
+ patch.detrend("time") # most proc funcs are methods
+ .pass_filter(time=(1, 100)) # units in Hz for time dim
+ .velocity_to_strain_rate()
+ )
+```
+
+## Decision table
+
+| Task | Use | Not |
+|---|---|---|
+| Read data files | `dc.spool(path)[0]` or iterate | `dc.read` (low-level) |
+| Discover file metadata cheaply | `dc.scan(path)` / `dc.scan_to_df(path)` | reading whole files |
+| Subset by time/distance values | `patch.select(time=(t1, t2))` | index math |
+| Subset by sample index | `patch.select(time=(0, 100), samples=True)` | index math on `patch.data` |
+| Merge contiguous patches | `spool.chunk(time=None)` | manual `np.concatenate` |
+| Fixed-length windows | `spool.chunk(time=30, overlap=5)` | manual slicing |
+| Parallel processing | `spool.map(func, client=executor)` | multiprocessing on patches directly |
+| Get example data | `dc.get_example_patch("random_das")` | downloading files in tests/docs |
+| Save patches | `patch.io.write(path, "dasdae")` or `dc.write(...)` | pickle |
+| Convert to ObsPy/xarray/pandas | `patch.io.to_obspy()` etc. | manual conversion |
+
+## Gotchas
+
+- **Patches are immutable.** `patch.pass_filter(...)` returns a new patch;
+ the original is unchanged. Chain calls or reassign.
+- **Time is numpy `datetime64`/`timedelta64`.** Use `dc.to_datetime64("2023-01-01")`
+ and `dc.to_timedelta64(1.5)` to build values; select accepts strings,
+ datetime64, and floats-as-seconds for relative offsets.
+- **`...` (Ellipsis) means "open ended"** in ranges: `time=(start, ...)`.
+- **Filter arguments are ranges keyed by dimension**:
+ `patch.pass_filter(time=(1, 100))` band-passes 1–100 Hz along the time dim;
+ `None` on one side makes it low/high pass.
+- **Units**: many methods accept pint quantities, e.g.
+ `patch.convert_units(distance="ft")`; `dc.get_quantity("10 m/s")` parses
+ strings. Do not cache pint quantities across registry resets.
+- **Directory spools need an index update** after files change:
+ `spool.update()`.
+- **`spool.select` on non-coordinate attrs** matches exact values or
+ collections (e.g. `network={"das1", "das2"}`).
+- **Processing functions live in `dascore.proc` but are attached to `Patch`
+ as methods**; transforms live in `dascore.transform`; plots in
+ `dascore.viz` (e.g. `patch.viz.waterfall()`).
+
+## Capability boundaries
+
+- DASCore handles single-experiment array data, not seismic network
+ workflows — use ObsPy for station/inventory-based seismology.
+- Supported formats and their read/scan/write capabilities are listed at
+ https://dascore.org/about/supported_formats.html — writing is only supported
+ for a few formats (DASDAE, and converters via ObsPy/xarray).
+- For very large datasets, prefer `spool.select(...).chunk(...)` before
+ loading; patches load lazily from directory spools.
+
+## Resources
+
+- Full documentation: https://dascore.org/
+- LLM index: https://dascore.org/llms.txt (full: /llms-full.txt)
+- Tutorials: https://dascore.org/tutorial/concepts.html
diff --git a/tests/test_changelog.py b/tests/test_changelog.py
index dd4d30509..9d905f693 100644
--- a/tests/test_changelog.py
+++ b/tests/test_changelog.py
@@ -2,61 +2,91 @@
Test which enforces that DASCore keeps no changelog.
Release notes are assembled from the pull requests merged since the last tag,
-each of which describes its own user-facing and breaking changes. The changelog
-page survives only as a stub preserving its published URL, so this test pins its
-contents exactly and fails if anything is added to it.
+each of which describes its own user-facing and breaking changes. The site's
+changelog page is generated at build time from the GitHub releases, so no
+changelog source belongs in the repository and this test fails if one appears.
"""
from __future__ import annotations
-# E501: the expected text below is a byte-for-byte copy of a markdown page whose
-# prose is deliberately not hard-wrapped; rewrapping it would break the match.
-# ruff: noqa: E501
import importlib.util
+import re
from pathlib import Path
import pytest
_REPO_ROOT = Path(__file__).resolve().parents[1]
-_DOC_PATH = _REPO_ROOT / "docs"
-_CHANGELOG_PATH = _DOC_PATH / "changelog.qmd"
_CHECKER_PATH = _REPO_ROOT / ".github" / "scripts" / "check_pr_changelog.py"
+_CONFIG_PATH = _REPO_ROOT / "great-docs.yml"
-# Run wherever the docs are, skip where they are not. The sdist grafts tests but
-# ships only docs/LICENSE, so the directory existing is not enough to tell the
-# two apart; index.qmd is present iff the real docs tree is. Deliberately not
-# keyed on changelog.qmd, or deleting the page would skip rather than fail.
-_DOCS_PRESENT = (_DOC_PATH / "index.qmd").is_file()
-
-_EXPECTED = """# Changelog
+# The directories the docs are written in, read from the configuration so a new
+# section is searched too, plus `docs` (assets the sections draw on) and the
+# repository root, which is where a hand-written changelog would most likely
+# land.
+_SECTION_RE = re.compile(r"^\s+dir:\s*(\S+)\s*$", re.MULTILINE)
-DASCore's changelog is the [releases page](https://github.com/DASDAE/dascore/releases), which tracks changes from one version to another.
-
-This page remains only so that existing links to it keep working; it is not part of the site navigation and is never updated per pull request. Each pull request describes its own user-facing and breaking changes, and those descriptions are collected into the release notes when a version is tagged. See [publish a new release](contributing/publish_a_new_release.qmd) for that workflow.
-"""
+# Run wherever the docs are, skip where they are not. The sdist grafts tests but
+# ships only docs/LICENSE, so a directory existing is not enough to tell the two
+# apart; the landing page is present iff the real docs tree is.
+_DOCS_PRESENT = (_REPO_ROOT / "index.qmd").is_file()
_POLICY = (
"DASCore does not keep a changelog. Describe user-facing and breaking "
"changes in the pull request that makes them, under the headings in "
".github/pull_request_template.md; they are collected into the release "
- "notes when a version is tagged. See .agents/agents.md. The page itself "
- "must stay exactly as pinned here, since it is published as "
- "dascore.org/changelog.html."
+ "notes when a version is tagged. See .agents/agents.md. The published "
+ "dascore.org/changelog.html page is generated from those releases by "
+ "great-docs, so writing one here would collide with it."
)
+def _doc_directories() -> list[Path]:
+ """Return the repository root and every directory a section is built from."""
+ config = _CONFIG_PATH.read_text(encoding="utf-8")
+ dirs = {"docs", *_SECTION_RE.findall(config)}
+ return [_REPO_ROOT, *(_REPO_ROOT / name for name in sorted(dirs))]
+
+
+def _changelog_sources() -> list[Path]:
+ """Return any file in the docs which is a changelog of its own."""
+ out = []
+ for directory in _doc_directories():
+ # The root is searched shallowly (its subdirectories are the sections,
+ # searched in their own right); a section, all the way down.
+ paths = directory.glob("*") if directory == _REPO_ROOT else directory.rglob("*")
+ for path in paths:
+ name = path.name.lower()
+ if name.startswith(("changelog", "change_log")):
+ out.append(path)
+ return sorted(out)
+
+
@pytest.mark.skipif(
not _DOCS_PRESENT,
reason="the docs are not present (e.g. running from an sdist)",
)
-class TestChangelogIsAStub:
- """The changelog page must stay a pointer to the releases page."""
-
- def test_contents_are_unchanged(self):
- """The page must match the expected stub exactly."""
- assert _CHANGELOG_PATH.exists(), f"{_CHANGELOG_PATH} is missing. {_POLICY}"
- contents = _CHANGELOG_PATH.read_text(encoding="utf-8")
- assert contents.strip() == _EXPECTED.strip(), _POLICY
+class TestNoChangelogSource:
+ """The changelog must come from the releases, not from a file."""
+
+ def test_no_changelog_page(self):
+ """No changelog source file may exist in the docs or repo root."""
+ found = _changelog_sources()
+ assert not found, f"{[str(p) for p in found]} should not exist. {_POLICY}"
+
+ def test_release_notes_are_configured(self):
+ """
+ The generated page needs the repository it draws releases from.
+
+ Dropping `repo` from the configuration would silently take the
+ changelog page with it, leaving dascore.org/changelog.html a 404.
+ """
+ text = _CONFIG_PATH.read_text(encoding="utf-8")
+ match = re.search(r'^repo:\s*"?(\S+?)"?\s*$', text, re.MULTILINE)
+ repo = match.group(1) if match else ""
+ assert "github.com" in repo, (
+ f"great-docs.yml must set `repo:` to the GitHub repository the "
+ f"changelog page is generated from, not {repo!r}. {_POLICY}"
+ )
def _load_checker():
diff --git a/tests/test_doc_coverage.py b/tests/test_doc_coverage.py
new file mode 100644
index 000000000..ddfa982fa
--- /dev/null
+++ b/tests/test_doc_coverage.py
@@ -0,0 +1,428 @@
+"""
+Tests that every public object in DASCore is represented in the documentation.
+
+The API reference in great-docs.yml is curated: entries are grouped by task
+rather than mirroring the package layout. Curation reads better than a generated
+dump, but it means new public API is invisible in the docs until someone lists
+it, and nothing about adding a function makes that omission noticeable. These
+tests compare DASCore's public surface against everything the docs cover and
+fail when an object is represented nowhere.
+
+An object counts as covered when it is listed in the reference, rendered on the
+utilities page, shown in the supported formats table, or named below as
+deliberately undocumented.
+
+Scope: the functions and classes a module defines or re-exports, and the public
+methods and properties of documented classes. Constants and type aliases are
+not checked, since they are rendered inline in signatures rather than given
+pages of their own.
+"""
+
+from __future__ import annotations
+
+import importlib
+import importlib.util
+import inspect
+import pkgutil
+from pathlib import Path
+
+import pytest
+
+import dascore as dc
+from dascore.io.core import FiberIO
+from dascore.utils.docs import iter_package_modules, iter_public
+
+# Not every job that collects this suite installs the docs/test extras; the
+# free-threaded and WASM workflows install pytest alone.
+yaml = pytest.importorskip("yaml")
+
+CONFIG_PATH = Path(dc.__file__).parents[1] / "great-docs.yml"
+
+# The package whose public objects are rendered onto a single page rather than
+# given a reference entry each. Must match utilities/index.qmd.
+SINGLE_PAGE_PACKAGE = "dascore.utils"
+
+# Modules whose contents are deliberately absent from the API reference. A
+# trailing ".*" exempts the submodules but not the module itself, so public API
+# added to dascore/io/__init__.py is still checked while reader internals
+# several levels down are not.
+UNREFERENCED_MODULES = {
+ "dascore.compat": "Shims over numpy/scipy, not DASCore's own API.",
+ "dascore.constants": "Type aliases and constants, rendered inline in signatures.",
+ "dascore.io.*": (
+ "Format reader internals. IO meant for users is re-exported at "
+ "dascore.io and listed in the reference; the readers themselves appear "
+ "in the supported formats table on about/supported_formats.qmd."
+ ),
+}
+
+# Individual objects deliberately absent, where the module they live in is
+# otherwise documented.
+UNREFERENCED_OBJECTS = {
+ "dascore.core.coords.ensure_consistent_dtype": "Coord construction helper.",
+ "dascore.core.coords.get_compatible_values": "Coord construction helper.",
+ "dascore.core.summary.normalize_source_patch_id": "Summary internal.",
+ "dascore.core.inventory.InventoryNames": "Names the inventory machinery fills in.",
+ "dascore.core.inventory.interval_masks": "Inventory coverage helper.",
+ "dascore.proc.inventory.get_attr_values": "Enrichment internal, not re-exported.",
+ "dascore.proc.inventory.resolve_contexts": "Enrichment internal, not re-exported.",
+ "dascore.proc.inventory.PlacedRow": "Inventory placement internal.",
+ "dascore.proc.inventory.RowEpochs": "Inventory placement internal.",
+ "dascore.proc.inventory.channel_placements": "Inventory placement internal.",
+ "dascore.proc.inventory.resolve_channel_pieces": "Inventory placement internal.",
+ "dascore.proc.inventory.resolve_row_epochs": "Inventory placement internal.",
+ "dascore.proc.inventory.resolve_split_pieces": "Inventory placement internal.",
+ "dascore.core.inventory_loader.load_directory": "Reached through dc.inventory().",
+ "dascore.models.registry.check_tag_matches": "Model tagging internal.",
+ "dascore.models.registry.get_model_tag": "Model tagging internal.",
+ "dascore.models.registry.resolve_model_tag": "Model tagging internal.",
+ "dascore.models.registry.resolve_tagged_model": "Model tagging internal.",
+}
+
+# Class members which are mechanism rather than API: pydantic validators run
+# during model construction and are never called by hand. Keyed by the class's
+# path in great-docs.yml, and inherited by subclasses.
+UNREFERENCED_MEMBERS = {
+ "PatchAttrs.reject_coordinate_attributes": "Pydantic validator.",
+ "proc.BaseCoord.check_time_units": "Pydantic validator.",
+}
+
+MISSING_MSG = """
+{count} public object(s) appear nowhere in the documentation:
+
+{listing}
+
+Every public object needs one of:
+ * an entry in the `reference:` section of great-docs.yml (the usual fix),
+ * an entry in that file's `exclude:` list, if discovery should skip it,
+ * a home in {package}, whose public objects are all rendered
+ automatically onto the utilities page,
+ * an entry in UNREFERENCED_MODULES or UNREFERENCED_OBJECTS in this file,
+ with a short reason, if it is public but intentionally undocumented.
+"""
+
+
+def _load_config() -> dict:
+ """Return the parsed great-docs configuration."""
+ return yaml.safe_load(CONFIG_PATH.read_text())
+
+
+def _walk_modules(package) -> list[str]:
+ """Return importable module names in a package, skipping private paths."""
+ out = [package.__name__]
+ prefix = f"{package.__name__}."
+ for info in pkgutil.walk_packages(package.__path__, prefix=prefix):
+ if not any(part.startswith("_") for part in info.name.split(".")):
+ out.append(info.name)
+ return out
+
+
+def _resolve(dotted: str):
+ """
+ Getattr-walk a dascore-relative dotted path, returning None if unreachable.
+
+ Reference entries must be reachable this way, not merely importable: griffe
+ resolves them with getattr, and one unreachable entry drops the whole
+ reference to static analysis.
+ """
+ obj = dc
+ for part in dotted.split("."):
+ obj = getattr(obj, part, None)
+ if obj is None:
+ return None
+ return obj
+
+
+def _key(obj):
+ """Return a hashable identity for an object, or None if it has none."""
+ obj = inspect.unwrap(obj)
+ try:
+ hash(obj)
+ except TypeError:
+ return None
+ return obj
+
+
+def _iter_public_safe(module_name: str) -> list:
+ """
+ Return a module's public objects, tolerating a missing optional dependency.
+
+ Only a package which is not installed at all is tolerated. An ImportError
+ naming a dascore module, or naming an installed one (a misspelled `from
+ numpy import ...`), is a broken import; swallowing it would drop every
+ object in that module from the comparison and quietly weaken the test.
+ """
+ try:
+ return list(iter_public(module_name))
+ except ImportError as e:
+ name = e.name or ""
+ if name.startswith("dascore") or _is_installed(name):
+ raise
+ return []
+
+
+def _is_installed(module_name: str) -> bool:
+ """Return True if the named package can be found in the environment."""
+ if not module_name:
+ return True
+ try:
+ return importlib.util.find_spec(module_name.split(".")[0]) is not None
+ except (ImportError, ValueError):
+ return True
+
+
+def _reexports(module_name: str) -> list:
+ """
+ Return public objects a package exposes but does not define.
+
+ Re-exporting is how a package states what it considers public, so these are
+ checked even when the module the object is defined in is exempt.
+ """
+ module = importlib.import_module(module_name)
+ out = []
+ for name in dir(module):
+ if name.startswith("_"):
+ continue
+ obj = getattr(module, name)
+ unwrapped = inspect.unwrap(obj)
+ if not (inspect.isfunction(unwrapped) or inspect.isclass(unwrapped)):
+ continue
+ if getattr(unwrapped, "__module__", "") != module_name:
+ out.append((name, obj))
+ return out
+
+
+def _public_surface() -> dict:
+ """Map each public object to the dotted path of the module defining it."""
+ out = {}
+ for module_name in _walk_modules(dc):
+ for name, obj in _iter_public_safe(module_name):
+ out.setdefault(inspect.unwrap(obj), f"{module_name}.{name}")
+ # Anything a package with an exempt subtree re-exports is public by
+ # intent, so record it under the package rather than where it is defined.
+ for prefix in UNREFERENCED_MODULES:
+ if not prefix.endswith(".*"):
+ continue
+ root = prefix[:-2]
+ for name, obj in _reexports(root):
+ out[inspect.unwrap(obj)] = f"{root}.{name}"
+ return out
+
+
+def _reference_entries(config) -> list[tuple[str, list[str] | None]]:
+ """
+ Return (dotted path, member names) for every reference entry.
+
+ Members are None for an entry which does not name them, which is how
+ great-docs is told to document them all; an entry naming an empty list
+ documents none of them, and is checked like any other explicit list.
+ """
+ out = []
+ for group in config.get("reference", []):
+ for item in group.get("contents", []):
+ if isinstance(item, str):
+ out.append((item, None))
+ else:
+ out.append((item["name"], item.get("members")))
+ return out
+
+
+def _documented(config) -> set:
+ """Return every object the documentation covers."""
+ out = set()
+ for dotted, members in _reference_entries(config):
+ obj = _resolve(dotted)
+ if obj is None:
+ continue
+ out.add(_key(obj))
+ for member in members or []:
+ sub = getattr(obj, member, None)
+ if sub is not None:
+ out.add(_key(sub))
+ # Walked the way the page renders it, so the two cannot disagree about
+ # which modules are covered.
+ for module_name in iter_package_modules(SINGLE_PAGE_PACKAGE):
+ out.update(_key(obj) for _, obj in _iter_public_safe(module_name))
+ for dotted in config.get("exclude", []):
+ out.add(_key(_resolve(dotted)))
+ out.discard(None)
+ return out
+
+
+def _accounted_members(config) -> dict:
+ """Map each documented class to the member names accounted for on it."""
+ out: dict = {}
+ for dotted, members in _reference_entries(config):
+ obj = _resolve(dotted)
+ if not inspect.isclass(obj):
+ continue
+ exempt = {
+ name.rsplit(".", 1)[1]
+ for name in UNREFERENCED_MEMBERS
+ if name.rsplit(".", 1)[0] == dotted
+ }
+ out.setdefault(obj, set()).update(members or [], exempt)
+ return out
+
+
+def _formats_table_entries() -> set:
+ """Return the reader classes the supported formats page lists."""
+ FiberIO.get_supported_io_table() # loads the plugins the table is built from
+ return {
+ type(fiberio)
+ for versions in FiberIO.manager._format_version.values()
+ for fiberio in versions.values()
+ }
+
+
+def _in_formats_table(obj, readers: set) -> bool:
+ """
+ Return True for a reader the supported formats page actually lists.
+
+ Subclassing FiberIO is not enough, and neither is sharing a registered
+ reader's name and version: the table lists the reader the registry holds,
+ so an unregistered one is documented nowhere.
+ """
+ return obj in readers
+
+
+def _member_origin(attr) -> str:
+ """
+ Return the module a class member comes from.
+
+ A property has no __module__ of its own, so ask the getter it wraps;
+ without this every new property would look like inherited third-party
+ machinery and be skipped.
+ """
+ if isinstance(attr, property):
+ attr = attr.fget
+ return getattr(attr, "__module__", "") or ""
+
+
+def _is_exempt(dotted: str) -> bool:
+ """Return True if policy says this object need not be documented."""
+ if dotted in UNREFERENCED_OBJECTS:
+ return True
+ module = dotted.rsplit(".", 1)[0]
+ for prefix in UNREFERENCED_MODULES:
+ if prefix.endswith(".*"):
+ if module.startswith(prefix[:-1]):
+ return True
+ elif module == prefix or module.startswith(f"{prefix}."):
+ return True
+ return False
+
+
+@pytest.fixture(scope="module")
+def config():
+ """The parsed great-docs configuration."""
+ if not CONFIG_PATH.exists():
+ pytest.skip("great-docs.yml is only present in a source checkout")
+ return _load_config()
+
+
+@pytest.fixture(scope="module")
+def documented(config):
+ """Every object covered by the documentation."""
+ return _documented(config)
+
+
+@pytest.fixture(scope="module")
+def surface():
+ """Every public object in DASCore, mapped to where it is defined."""
+ return _public_surface()
+
+
+class TestDocCoverage:
+ """Ensure the curated documentation keeps pace with the public API."""
+
+ def test_every_public_object_is_documented(self, documented, surface):
+ """Public objects must be represented somewhere in the docs."""
+ table = _formats_table_entries()
+ missing = {
+ dotted
+ for obj, dotted in surface.items()
+ if obj not in documented
+ and not _in_formats_table(obj, table)
+ and not _is_exempt(dotted)
+ }
+ if missing:
+ listing = "\n".join(f" {name}" for name in sorted(missing))
+ msg = MISSING_MSG.format(
+ count=len(missing), listing=listing, package=SINGLE_PAGE_PACKAGE
+ )
+ pytest.fail(msg)
+
+ def test_reference_entries_resolve(self, config):
+ """
+ Every reference entry must be reachable by getattr from dascore.
+
+ The doc build fails on a deleted object, but slowly and with a less
+ direct message; an entry that is importable yet not attribute-reachable
+ does something worse, silently degrading the whole reference to static
+ analysis so templated docstrings render as literal placeholders.
+ """
+ unresolved = [
+ dotted
+ for dotted, _ in _reference_entries(config)
+ if _resolve(dotted) is None
+ ]
+ assert not unresolved, (
+ f"great-docs.yml lists objects which no longer exist or are not "
+ f"reachable as attributes of dascore: {sorted(unresolved)}"
+ )
+
+ def test_listed_members_exist(self, config):
+ """Members named in the reference must exist on their class."""
+ missing = []
+ for dotted, members in _reference_entries(config):
+ obj = _resolve(dotted)
+ if obj is None:
+ continue
+ missing += [
+ f"{dotted}.{m}" for m in members or [] if getattr(obj, m, None) is None
+ ]
+ assert not missing, (
+ f"great-docs.yml names members which no longer exist: {sorted(missing)}"
+ )
+
+ def test_public_members_are_documented(self, config, documented):
+ """
+ Public members of documented classes must themselves be covered.
+
+ A class listed with an explicit `members` list shows only those members,
+ so a new method is invisible unless it is added there. A member is also
+ covered when it is documented in its own right (patch functions are
+ attached to Patch as methods), when an ancestor's entry already accounts
+ for the name (Spool.chunk overrides the documented BaseSpool.chunk), or
+ when its type is documented, which is how the generated arithmetic
+ methods on Patch are covered: they are all instances of PatchUFunc,
+ whose page describes what every one of them does.
+ """
+ accounted = _accounted_members(config)
+ missing = []
+ for dotted, members in _reference_entries(config):
+ obj = _resolve(dotted)
+ if not inspect.isclass(obj) or members is None:
+ continue
+ names = set().union(*(accounted.get(a, set()) for a in obj.__mro__))
+ for name in dir(obj):
+ if name.startswith("_") or name in names:
+ continue
+ attr = inspect.unwrap(inspect.getattr_static(obj, name, None))
+ if not _member_origin(attr).startswith("dascore"):
+ continue # inherited from pydantic, etc.
+ if _key(attr) in documented or type(attr) in documented:
+ continue
+ missing.append(f"{dotted}.{name}")
+ assert not missing, (
+ f"Public members are missing from the `members` lists in "
+ f"great-docs.yml: {sorted(missing)}"
+ )
+
+ def test_exclude_list_is_current(self, config):
+ """Excluded names must still exist, so the list does not go stale."""
+ stale = [d for d in config.get("exclude", []) if _resolve(d) is None]
+ assert not stale, (
+ f"great-docs.yml excludes objects which no longer exist: {sorted(stale)}"
+ )
diff --git a/tests/test_utils/test_doc_utils.py b/tests/test_utils/test_doc_utils.py
index 53527fd28..c3124cce1 100644
--- a/tests/test_utils/test_doc_utils.py
+++ b/tests/test_utils/test_doc_utils.py
@@ -2,20 +2,27 @@
from __future__ import annotations
+import importlib
+import importlib.util
import textwrap
import pandas as pd
import pytest
+import dascore.utils.docs as docs_module
import dascore.utils.namespace as ns_module
from dascore.core.attrs import PatchAttrs
from dascore.examples import EXAMPLE_PATCHES
from dascore.utils.docs import (
compose_docstring,
format_dtypes,
+ get_doc_anchor,
get_docstring,
get_plugin_table,
+ iter_public,
objs_to_doc_df,
+ render_module_api,
+ render_package_api,
)
@@ -153,3 +160,103 @@ def test_example_no_cross_ref(self):
df = objs_to_doc_df(EXAMPLE_PATCHES, cross_reference=False)
assert "(`dascore.examples" not in df["Name"].iloc[0]
assert isinstance(df, pd.DataFrame)
+
+
+class TestRenderApi:
+ """Tests for rendering a module's API onto one documentation page."""
+
+ # griffe parses the docstrings and ships with the doc build, not with
+ # dascore; the minimal-dependency, free-threaded and WASM jobs lack it.
+ pytestmark = pytest.mark.skipif(
+ importlib.util.find_spec("griffe") is None,
+ reason="griffe is only installed with the doc build",
+ )
+
+ @pytest.fixture(scope="class")
+ def misc_markdown(self):
+ """Rendered markdown for a module with a mix of documented objects."""
+ return render_module_api("dascore.utils.misc")
+
+ def test_anchor_is_stable_and_html_safe(self):
+ """Anchors must be reproducible so the inventory can point at them."""
+ anchor = get_doc_anchor("dascore.utils.misc.iterate")
+ assert anchor == "dascore-utils-misc-iterate"
+ assert anchor == get_doc_anchor("dascore.utils.misc.iterate")
+
+ def test_entry_has_anchor_signature_and_summary(self, misc_markdown):
+ """Each entry needs the pieces a reader and the inventory rely on."""
+ assert "#### iterate {#dascore-utils-misc-iterate}" in misc_markdown
+ assert "iterate(" in misc_markdown
+
+ def test_details_are_collapsed(self, misc_markdown):
+ """Parameters and examples sit in a collapsed callout, not inline."""
+ assert 'collapse="true"' in misc_markdown
+ assert "| Parameter | Type | Description |" in misc_markdown
+
+ def test_only_objects_defined_in_the_module(self, misc_markdown):
+ """Imported names belong to the module which defines them."""
+ # misc imports numpy as np; it should not document numpy.
+ assert "#### np " not in misc_markdown
+
+ def test_package_render_covers_every_module(self):
+ """Every non-private module with public objects gets a section."""
+ markdown = render_package_api("dascore.utils")
+ assert "### dascore.utils.misc {#dascore-utils-misc}" in markdown
+ assert "### dascore.utils.patch {#dascore-utils-patch}" in markdown
+
+ def test_decorated_helpers_are_documented(self):
+ """Cached helpers are callables, not functions, and must not vanish."""
+ names = {name for name, _ in iter_public("dascore.utils.downloader")}
+ assert "get_registry_df" in names
+
+ def test_griffe_models_never_reach_the_page(self):
+ """Unhandled docstring sections must not render object reprs."""
+ # ChunkPlan documents an Attributes section, which has its own model.
+ markdown = render_module_api("dascore.utils.chunk_plan")
+ assert "object at 0x" not in markdown
+
+ def test_an_example_which_is_a_code_block_stays_one_block(self):
+ """A fenced example must not close the fence the page wraps it in."""
+ # compose_docstring's example is itself a fenced block. Wrapping it in
+ # a fence of the same length ends the block at the example's own
+ # opening fence, so the example body lands outside any block and the
+ # rest of the page is swallowed by the next fence it meets.
+ markdown = render_module_api("dascore.utils.docs")
+ inside, fence = set(), ""
+ for line in markdown.splitlines():
+ if line.startswith("```") and not fence:
+ fence = line[: len(line) - len(line.lstrip("`"))]
+ elif fence and line.strip() == fence:
+ fence = ""
+ elif fence:
+ inside.add(line)
+ assert not fence, "a code block was left open"
+ assert "from dascore.utils.docs import compose_docstring" in inside
+
+ def test_examples_are_shown_rather_than_executed(self):
+ """A `{python}` cell would run when the page is rendered."""
+ markdown = render_package_api("dascore.utils")
+ assert "```{python}" not in markdown
+
+ def test_a_module_needing_a_missing_dependency_is_skipped(self, monkeypatch):
+ """One unimportable module must not take the rest of the page with it."""
+ real = importlib.import_module
+
+ def fake(name, *args, **kwargs):
+ if name == "dascore.utils.misc":
+ raise ImportError("no optional dependency here")
+ return real(name, *args, **kwargs)
+
+ monkeypatch.setattr(docs_module.importlib, "import_module", fake)
+ markdown = render_package_api("dascore.utils")
+ assert "### dascore.utils.misc" not in markdown
+ assert "### dascore.utils.patch {#dascore-utils-patch}" in markdown
+
+ def test_every_callout_is_closed(self):
+ """An unclosed callout swallows the entries which follow it."""
+ markdown = render_package_api("dascore.utils")
+ lines = markdown.splitlines()
+ opened = sum(1 for line in lines if line.startswith(":::") and "{" in line)
+ closed = sum(1 for line in lines if set(line.strip()) == {":"})
+ assert opened == closed
+ assert "| Attribute | Type | Description |" in markdown
diff --git a/docs/tutorial/concepts.qmd b/tutorial/concepts.qmd
similarity index 97%
rename from docs/tutorial/concepts.qmd
rename to tutorial/concepts.qmd
index 07b50530c..22cf3e493 100644
--- a/docs/tutorial/concepts.qmd
+++ b/tutorial/concepts.qmd
@@ -6,7 +6,7 @@ execute:
This page highlights some concepts helpful for working with DASCore.
-# Data structures
+## Data structures
For most uses of DASCore, only two data structures are directly involved.
These are the [Patch](patch.qmd) and the [Spool](spool.qmd).
@@ -15,11 +15,11 @@ The `Patch` contains a contiguous block of N dimensional data and metadata.
The `Spool` manages a group of `Patch`es. These can be in memory, on
disk, or a remote resource.
-{#fig-patch_n_spool}
+{#fig-patch_n_spool}
You will read more about Patches and Spools later on in the tutorial.
-# Time
+## Time
Any expression of time should use [numpy](https://numpy.org/doc/stable/reference/arrays.datetime.html) time constructs, which include [datetime64](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.datetime64) and [timedelta64](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.timedelta64).
@@ -56,7 +56,7 @@ time_2 = dc.to_datetime64(610243200)
```
-# Dimension Selection
+## Dimension Selection
Most of DASCore's processing methods can be applied along any dimension. Typically, the dimension is selected with keyword, and the method specific data are passed as values. For example, applying [pass_filter](`dascore.proc.filter.pass_filter`) to a patch with `distance` and `time` dimensions works like this:
@@ -108,7 +108,7 @@ assert np.allclose(
When in doubt, be explicit with units and read the docs for the function in question!
-# Units
+## Units
DASCore provides first class support for units through the [`units`](`dascore.units`) module. Units (or rather quantities) can be imported directly or can be created with the [get_quantity](`dascore.units.get_quantity`) function.
diff --git a/docs/tutorial/configuration.qmd b/tutorial/configuration.qmd
similarity index 100%
rename from docs/tutorial/configuration.qmd
rename to tutorial/configuration.qmd
diff --git a/docs/tutorial/coords.qmd b/tutorial/coords.qmd
similarity index 97%
rename from docs/tutorial/coords.qmd
rename to tutorial/coords.qmd
index b04f7c667..9c37a9e98 100644
--- a/docs/tutorial/coords.qmd
+++ b/tutorial/coords.qmd
@@ -10,7 +10,7 @@ This page covers advanced DASCore features. Most users will be fine with only th
In order to manage coordinate labels and array manipulations, DASCore implements two classes, [`BaseCoord`](`dascore.core.coords.BaseCoord`), which has several associated subclasses corresponding to different types of coordinates, and [CoordManager](`dascore.core.coordmanager.CoordManager`) which manages a group of coordinates. Much like the [`Patch`](`dascore.core.patch.Patch`), instances of both of these classes are immutable (to the extent possible), so they cannot be modified in place but have methods which return new instances.
-# Coordinates
+## Coordinates
Coordinates usually keep track of labels along an associated dimension of an array, but they can also be independent of array data. They provide methods for slicing, re-ordering, filtering etc. and are used internally by DASCore for such operations.
@@ -21,9 +21,9 @@ Much like DASCore's [`Spool`](`dascore.core.spool.BaseSpool`), Coordinates are a
Coordinates are very similar (in concept) to Pandas' indices, with some significant differences in implementation.
:::
-## Coordinate Creation
+### Coordinate Creation
-### Get Coord
+#### Get Coord
[`get_coord`](`dascore.core.coords.get_coord`) returns an instance of a subclass of [`BaseCoord`](`dascore.core.coords.BaseCoord`) appropriate for the input values. Here are a few examples:
@@ -62,7 +62,7 @@ print(type(coord).__name__)
print(coord.values)
```
-### Segmented Coordinates
+#### Segmented Coordinates
[`CoordSegmented`](`dascore.core.coords.CoordSegmented`) preserves monotonic coordinate blocks and the discontinuities between them. It is useful when a logical coordinate contains dropped samples, acquisition gaps, or a sampling-rate change but the corresponding data should remain in one in-memory patch.
@@ -95,7 +95,7 @@ assert len(exact_coord.get_discontinuities("gaps")) == 1
Segmented coordinates have `step=None` because one step cannot describe the entire coordinate. `coord.simplify(tolerance=...)` may replace segments with a simpler fit while bounding how far values move; `coord.snap()` always forces one evenly sampled range and can move interior values without a bound. Use `patch.split_gaps()` to turn a patch with segmented dimensional coordinates into contiguous patches before writing it to a format that cannot store gaps.
-### Update
+#### Update
Update uses the existing coordinate as a template and returns a coordinate with some part modified.
@@ -122,7 +122,7 @@ coord_new_max = coord.update(max=stop + 10 * step)
```
-## Coordinate Attributes
+### Coordinate Attributes
The following tables shows some of the commonly used coordinate attributes:
@@ -140,11 +140,11 @@ The following tables shows some of the commonly used coordinate attributes:
: Coordinate attributes {.striped .hover}
-## Coordinate Methods
+### Coordinate Methods
This section highlights some of the coordinate methods. The methods which would cause changes to a data array return a new coordinate and an object that can be used for indexing an array. This can either be a `slice` instance or another array which uses numpy's advanced indexing features for sorting or selection.
-### Sort
+#### Sort
[`sort`](`dascore.core.coords.BaseCoord.sort`) sorts the values of the coordinate.
```{python}
@@ -164,7 +164,7 @@ data = np.random.rand(10, 20)
sorted_data = data[indexer, :]
```
-### Snap
+#### Snap
[`snap`](`dascore.core.coords.BaseCoord.snap`) replaces coordinate values with an evenly sampled range spanning the same minimum and maximum. This intentionally loses interior precision and should be used only when that idealization is acceptable. Unlike [`sort`](`dascore.core.coords.BaseCoord.sort`), it returns only a new coordinate and no indexer, because it replaces the values rather than permuting them, so for an unsorted coordinate the snapped values no longer label the samples they used to. When data alignment matters, use [`CoordManager.snap`](`dascore.core.coordmanager.CoordManager.snap`), which sorts the coordinate and its associated array together before snapping, or [`Patch.snap_coords(...)`](`dascore.Patch.snap_coords`), which does the same across the selected dimensions of a patch.
@@ -185,7 +185,7 @@ assert snapped_coord.max() == irregular_coord.max()
Use `simplify(tolerance=...)` instead when the maximum allowed coordinate movement must be explicit. Most coordinate types are already in their simplest form; this distinction is most useful for segmented coordinates.
-### Select
+#### Select
[`select`](`dascore.core.coords.BaseCoord.select`) is used for slicing/sub-selecting.
```{python}
@@ -215,7 +215,7 @@ new_coord, indexer = coord.select((14*ft, 50 * ft))
print(new_coord)
```
-## String Coordinates
+### String Coordinates
String coordinates support exact matching, wildcard matching with `*` and `?`,
compiled regular expressions, boolean-mask selection, and sorting. They are
@@ -260,7 +260,7 @@ compiled regular expressions, or boolean masks. Strings containing `*` or `?`
are treated as wildcard patterns, so avoid those characters in labels you
intend to select literally.
-### Units
+#### Units
[`convert_units`](`dascore.core.coords.BaseCoord.convert_units`) and [`set_units`](`dascore.core.coords.BaseCoord.set_units`) are used to change/set the units associated with a coordinate.
@@ -288,7 +288,7 @@ print(f"Simplified units are: {simple_coord.units}")
print(f"New coord lims are: {simple_coord.limits}")
```
-### Get Next Index
+#### Get Next Index
[`get_next_index`](`dascore.core.coords.BaseCoord.get_next_index`) returns the index value (an integer) for where a value would be inserted into the coordinate. It can only be used on a sorted coordinate.
```{python}
@@ -300,12 +300,12 @@ assert coord.get_next_index(1) == 1
assert coord.get_next_index(2.000001) == 3
```
-# CoordManager
+## CoordManager
The [`CoordManager`](`dascore.core.coordmanager.CoordManager`) handles a group of coordinates and provides methods for updating managed data arrays.
-## Coordinate Manager Creation
+### Coordinate Manager Creation
[`CoordManager`](`dascore.core.coordmanager.CoordManager`) instances can be created from a dictionary of coordinates via the [`get_coord_manager`](`dascore.core.coordmanager.get_coord_manager`) function.
```{python}
@@ -349,7 +349,7 @@ cm_many_coords = get_coord_manager(coords=coord_dict, dims=("dim1", "dim2"))
print(cm_many_coords)
```
-### Update
+#### Update
[`update`](`dascore.core.coordmanager.CoordManager.update`) uses an existing `CoordinateManager` as a template and updates some aspect in the returned coordinate.
@@ -400,11 +400,11 @@ new_cm_5 = cm.update(time=None)
assert "time" not in new_cm_5.dims
```
-## Coordinate Manager Methods
+### Coordinate Manager Methods
Much like `BaseCoord`, the `CoordinateManager` class implements a variety of methods for filtering, sorting, modifying units, etc. However, there are some difference. Unlike coordinates, when an operation would change the data array associated with the coordinates, the `CoordManager` method accepts the array as an argument and returns a new array. Like the `Patch` methods, `CoordManager` methods use keyword arguments to specify coordinates by name.
-### Select
+#### Select
[select](`dascore.core.coordmanager.CoordManager.select`) trims the coordinate manager and, optionally, an associated array.
@@ -418,7 +418,7 @@ new_cm, new_data = cm.select(data=data, distance=(..., 100))
```
-### Sort
+#### Sort
[sort](`dascore.core.coordmanager.CoordManager.sort`) sorts along one or more axes.
@@ -432,7 +432,7 @@ cm, data = patch.coords, patch.data
new_cm, new_data = cm.sort("time", "distance", reverse=True)
```
-### Rename Coord
+#### Rename Coord
[rename_coord](`dascore.core.coordmanager.CoordManager.rename_coord`) renames a coordinate or dimension.
diff --git a/docs/tutorial/file_io.qmd b/tutorial/file_io.qmd
similarity index 98%
rename from docs/tutorial/file_io.qmd
rename to tutorial/file_io.qmd
index 28d558e98..da238781e 100644
--- a/docs/tutorial/file_io.qmd
+++ b/tutorial/file_io.qmd
@@ -36,7 +36,7 @@ print(loaded.dims)
## Writing Patches to Disk
-Patches can be written to disk using the `io` namespace. The following shows how to write a Patch to disk in the [DASDAE format](`dascore.io.dasdae`)
+Patches can be written to disk using the `io` namespace. The following shows how to write a Patch to disk in the [DASDAE format](https://github.com/DASDAE/dascore/tree/master/dascore/io/dasdae)
```{python}
from pathlib import Path
diff --git a/docs/tutorial/patch.qmd b/tutorial/patch.qmd
similarity index 89%
rename from docs/tutorial/patch.qmd
rename to tutorial/patch.qmd
index 9e7b9b33c..3720302eb 100644
--- a/docs/tutorial/patch.qmd
+++ b/tutorial/patch.qmd
@@ -10,11 +10,11 @@ A [`Patch`](`dascore.core.patch.Patch`) manages an array and its associated coor
The `Patch` design was inspired by [Xarray's `DataArray`](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.html)
:::
-# Patch creation
+## Patch creation
Patches can be created in several different ways.
-## Load an example patch
+### Load an example patch
DASCore includes several example datasets. They are mostly used for simple demonstrations and testing.
@@ -28,7 +28,7 @@ pa2 = dc.get_example_patch("example_event_1")
See [`get_example_patch`](`dascore.examples.get_example_patch`) for supported patches.
-## Load a file
+### Load a file
A single file can be loaded like this:
@@ -59,7 +59,7 @@ pa = dc.spool(path)[0]
Spools are covered in more detail in the [next section](spool.qmd).
-## Manually create a patch
+### Manually create a patch
Patches can be created from:
@@ -100,9 +100,9 @@ dims = ('distance', 'time')
pa = dc.Patch(data=array, coords=coords, attrs=attrs, dims=dims)
```
-# Patch anatomy
+## Patch anatomy
-## Data
+### Data
The data is simply an n-dimensional array which is accessed with the `data` attribute.
@@ -130,7 +130,7 @@ array[:10] = 12 # then this works
:::
-## Coords
+### Coords
DASCore implements a class called [CoordManager](`dascore.core.coordmanager.CoordManager`) which manages dimension names, coordinate labels, selecting, sorting, etc. `CoordManager` has several convenience methods for accessing contained information:
@@ -216,7 +216,7 @@ compiled regular expressions, boolean-mask selection, and sorting, but they do
not support units or numeric range selection. Avoid using `*` or `?` in labels
you intend to select literally.
-## Attrs
+### Attrs
The metadata stored in `Patch.attrs` is a [pydantic model](https://docs.pydantic.dev/usage/models/) which enforces a schema and provides validation. [`PatchAttrs.get_summary_df`](`dascore.models.base.DascoreBaseModel.get_summary_df`) generates a table of the attribute descriptions:
@@ -238,7 +238,7 @@ Markdown(df_str)
Specific data formats may also add attributes (e.g. "gauge_length", "pulse_width"), but this depends on the parser.
-### Patch History
+#### Patch History
Patch-processing methods append entries to `Patch.attrs.history` by default so downstream steps remain traceable.
@@ -267,7 +267,7 @@ assert out.attrs.history == old_history
`Patch.attrs` only stores non-coordinate metadata. This includes things like the acquisition key, data units, and observing-system fields such as `gauge_length` or `pulse_width`.
-## Summary
+### Summary
`Patch.summary` provides a read-only combined view of:
@@ -296,7 +296,7 @@ print(row["distance_max"])
The `data_type` attribute is an optional label for the kind of data in the patch. It is useful for display defaults and quick inspection, but physical interpretation should come from `data_units`, coordinate units, and `history`. See the [PatchAttrs note](../notes/patch_attrs.qmd) for more detail.
-## String representation
+### String representation
DASCore Patches have a useful string representation:
@@ -307,7 +307,7 @@ patch = dc.get_example_patch()
print(patch)
```
-## Shortcuts
+### Shortcuts
DASCore Patches offer a few shortcuts for quickly accessing commonly used information:
@@ -333,13 +333,13 @@ patch_name = patch.get_patch_name()
patch_names = spool.get_patch_names()
```
-# Trim and Reshape
+## Trim and Reshape
The following methods help trim, reshape, and manipulate coordinates.
-## Select
+### Select
-Patches are trimmed using the [`Patch.select`](`dascore.Patch.select`) method. Unlike [`Patch.order`](`dascore.Patch.order`), `select` will not change the order of the affected dimensions, it will only remove elements. Most commonly, `select` takes the coordinate name and a tuple of (lower_limit, upper_limit) as the values. Either limit can be `...` or `None`, indicating an open interval, as can an infinite bound pointing away from the data, such as `(lower_limit, np.inf)`.
+Patches are trimmed using the [`Patch.select`](`dascore.proc.select`) method. Unlike [`Patch.order`](`dascore.proc.order`), `select` will not change the order of the affected dimensions, it will only remove elements. Most commonly, `select` takes the coordinate name and a tuple of (lower_limit, upper_limit) as the values. Either limit can be `...` or `None`, indicating an open interval, as can an infinite bound pointing away from the data, such as `(lower_limit, np.inf)`.
```{python}
import numpy as np
@@ -438,7 +438,7 @@ sub_patch = patch.select(distance=np.array([0, 12, 10, 9]), samples=True)
assert len(sub_patch.get_array('distance')) == 4
```
-## Unselect
+### Unselect
[`Patch.unselect`](`dascore.Patch.unselect`) is the complement of `select`: it takes the same selectors and removes the samples `select` would have kept. Naming one coordinate gives exactly the complement; naming several complements each on its own, as below.
@@ -464,8 +464,8 @@ assert outside.get_coord("distance").step is None
That is why [`Spool.unselect`](`dascore.core.spool.Spool.unselect`) refuses the patches' own coordinates. A patch can have samples removed from its middle; at spool level the complement of a range would be a hole in every patch rather than a choice between patches. The coordinates an attached DASDAE inventory defines along the fiber are a separate case, and are accepted: removing one of those chooses which channels a patch holds. When several coordinates are named, each is complemented on its own — the true complement of a block is a frame around it, which no array can hold.
-## Order
- Order is similar to [`Patch.select`](`dascore.Patch.select`), but will re-arrange data to the order specified by a value array. This may also cause parts of the patch to be duplicated.
+### Order
+ Order is similar to [`Patch.select`](`dascore.proc.select`), but will re-arrange data to the order specified by a value array. This may also cause parts of the patch to be duplicated.
```{python}
import numpy as np
@@ -485,10 +485,10 @@ patch_dist2 = patch.order(distance=dist_order2)
assert np.all(dist_order2 == patch_dist2.get_array("distance"))
```
-## New dimensions
+### New dimensions
Sometimes it can be useful to add new (empty) dimensions to a Patch.
-[`Patch.append_dims`](`dascore.Patch.append_dims`) does this.
+[`Patch.append_dims`](`dascore.proc.append_dims`) does this.
```python
import dascore as dc
@@ -512,7 +512,7 @@ patch_extended_coord = patch_extended.update_coords(money=[10, 30])
Although these examples are quite contrived, these functions are very useful for transforms which create high dimensional patches.
-# Processing
+## Processing
The patch has several methods which are intended to be chained together via a [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface), meaning each method returns a new `Patch` instance.
@@ -529,11 +529,11 @@ out = (
.pass_filter(time=(..., 10))
)
```
-The processing methods are located in the [dascore.proc](`dascore.proc`) module. The [patch processing tutorial](processing.qmd) provides more information about processing routines.
+The processing methods are located in the [dascore.proc](/reference/index.qmd#patch-processing) module. The [patch processing tutorial](processing.qmd) provides more information about processing routines.
-# Visualization
+## Visualization
-DASCore provides some visualization functions in the [dascore.viz](`dascore.viz`) module or using the `Patch.viz` namespace. DASCore generally only implements simple, matplotlib based visualizations but other DASDAE packages will likely do more interesting visualizations.
+DASCore provides some visualization functions in the [dascore.viz](/reference/index.qmd#visualization) module or using the `Patch.viz` namespace. DASCore generally only implements simple, matplotlib based visualizations but other DASDAE packages will likely do more interesting visualizations.
```{python}
import dascore as dc
@@ -547,13 +547,13 @@ patch = (
patch.viz.waterfall(show=True);
```
-# Modifying patches
+## Modifying patches
Because patches should be treated as immutable objects, they can't be modified with normal attribute assignment. However, DASCore provides several methods that return new patches with modifications.
-## Update
+### Update
-[`Patch.update`](`dascore.core.patch.Patch.update`) uses the `Patch` instances as a template and returns a new `Patch` instances with one or more aspects modified.
+[`Patch.update`](`dascore.proc.update`) uses the `Patch` instances as a template and returns a new `Patch` instances with one or more aspects modified.
```{python}
import dascore as dc
@@ -566,9 +566,9 @@ new_data_patch = pa.update(data=pa.data * 10)
new_attrs_patch = pa.update(attrs=dict(tag="TMU"))
```
-## Update attrs
+### Update attrs
-[`Patch.update_attrs`](`dascore.core.patch.Patch.update_attrs`) is for making changes to the attrs (metadata) while keeping the unaffected metadata (`Patch.update` would completely replace the old attrs).
+[`Patch.update_attrs`](`dascore.proc.update_attrs`) is for making changes to the attrs (metadata) while keeping the unaffected metadata (`Patch.update` would completely replace the old attrs).
```{python}
import dascore as dc
@@ -585,15 +585,15 @@ assert pa1.attrs.new_attr == 42
```
-## Update coords
+### Update coords
-[`Patch.update_coords`](`dascore.core.patch.Patch.update_coords`) returns a new patch with the coordinates changed in some way. These changes can include:
+[`Patch.update_coords`](`dascore.proc.update_coords`) returns a new patch with the coordinates changed in some way. These changes can include:
- Modifying (updating) existing coordinates
- Adding new coordinates
- Changing coordinate dimensional association
-### Modifying coordinates
+#### Modifying coordinates
Coordinates can be updated by specifying a new array which should take the place of the old one:
@@ -621,7 +621,7 @@ new_time = pa.coords.min('time') + one_second
new = pa.update_coords(time_min=new_time)
```
-### Adding coordinates
+#### Adding coordinates
Commonly, additional coordinates, such as latitude/longitude, are attached to a particular dimension such as distance. It is also possible to include coordinates that are not associated with any dimensions.
@@ -658,7 +658,7 @@ out_3 = pa.update_coords(
no_dim_coord = pa.update_coords(non_dim=(None, np.arange(10)))
```
-### Changing coordinate dimensional association
+#### Changing coordinate dimensional association
The dimensions each coordinate is associated with can be changed. For example, to remove a coordinate's dimension association:
@@ -673,7 +673,7 @@ lat = patch.coords.get_array('latitude')
patch_detached_lat = patch.update_coords(latitude=(None, lat))
```
-## Dropping coordinates
+### Dropping coordinates
Non-dimensional coordinates can be dropped using [`Patch.drop_coords`](`dascore.proc.coords.drop_coords`). Dimensional coordinates, however, cannot be dropped since doing so would force the patch data to become degenerate.
@@ -688,7 +688,7 @@ patch_dropped_lat = patch.drop_coords("latitude")
print(patch_dropped_lat.coords)
```
-### Coords in patch initialization
+#### Coords in patch initialization
Any number of coordinates can also be assigned when the patch is initiated. For coordinates other than those of the patch dimensions, the associated dimensions must be specified. For example:
@@ -725,17 +725,17 @@ out = dc.Patch(data=array, coords=coords, attrs=attrs, dims=dims)
```
-# Units
+## Units
As mentioned in the [units section of the concept page](concepts.qmd#units), DASCore provides first-class support for units.
-## Patch units
+### Patch units
There are two methods for configuring the units associated with a `Patch`.
-[`Patch.set_units`](`dascore.Patch.set_units`) sets the units on a patch or its coordinates. Old units are simply overwritten without performing any conversions. The first argument sets the data units and the keywords set the coordinate units.
+[`Patch.set_units`](`dascore.proc.set_units`) sets the units on a patch or its coordinates. Old units are simply overwritten without performing any conversions. The first argument sets the data units and the keywords set the coordinate units.
-[`Patch.convert_units`](`dascore.Patch.convert_units`) converts data or coordinates units by appropriately transforming the data or coordinates arrays. If no units exist they will simply be set.
+[`Patch.convert_units`](`dascore.proc.convert_units`) converts data or coordinates units by appropriately transforming the data or coordinates arrays. If no units exist they will simply be set.
```{python}
import dascore as dc
@@ -762,7 +762,7 @@ print(type(patch.attrs.data_units))
print(get_quantity_str(patch.attrs.data_units))
```
-## Units in processing functions
+### Units in processing functions
```{python}
import dascore as dc
@@ -777,9 +777,9 @@ sub_selected = pa.select(distance=(10*ft, 10*m))
dist_filtered = pa.pass_filter(distance=(10*m, 100*m))
```
-See the documentation on [`Patch.select`](`dascore.Patch.select`) and [`Patch.pass_filter`](`dascore.Patch.pass_filter`) for more details.
+See the documentation on [`Patch.select`](`dascore.proc.select`) and [`Patch.pass_filter`](`dascore.proc.pass_filter`) for more details.
-# Patch operations
+## Patch operations
Patches implement many numpy-like functions which are applied directly to a patch using built-in python operators.
@@ -789,7 +789,7 @@ In the case of scalars and numpy arrays, the operations are broadcast over the p
See [`merge_compatible_coords_attrs`](`dascore.utils.patch.merge_compatible_coords_attrs`) for more details on how attributes and coordinates are handled when performing operations on two patches.
:::
-## Patch operations with scalars
+### Patch operations with scalars
```{python}
import numpy as np
@@ -823,7 +823,7 @@ print(f"units before operation {patch.attrs.data_units}")
print(f"units after operation {new.attrs.data_units}")
```
-## Patch operations with numpy arrays
+### Patch operations with numpy arrays
`Patch` implements the numpy array protocol, meaning you can use numpy functions on patches
@@ -855,9 +855,9 @@ print(f"units before operation {patch.attrs.data_units}")
print(f"units after operation {out1.attrs.data_units}")
```
-## Patch operations with other patches
+### Patch operations with other patches
-### Identically shaped patches
+#### Identically shaped patches
When patches are shaped the same, the operations can simply be applied on the data arrays, then coords and attrs merged.
@@ -876,7 +876,7 @@ out = patch + patch
assert np.allclose(patch.data * 2, out.data)
```
-### Broadcastable patches
+#### Broadcastable patches
If the arrays are not shaped the same, but they can be broadcasted to compatible shapes, these operations work as well.
@@ -895,7 +895,7 @@ sum_patch = patch + sub_patch
print(sum_patch.shape)
```
-### Numpy Functions
+#### Numpy Functions
DASCore uses [apply_array_ufunc](`dascore.utils.array.apply_ufunc`) and [array_function](`dascore.utils.array.apply_array_func`) for applying numpy functions to `Patch` instances.
diff --git a/docs/tutorial/processing.qmd b/tutorial/processing.qmd
similarity index 80%
rename from docs/tutorial/processing.qmd
rename to tutorial/processing.qmd
index eaadfd64d..4d4964da8 100644
--- a/docs/tutorial/processing.qmd
+++ b/tutorial/processing.qmd
@@ -4,14 +4,14 @@ execute:
warning: false
---
The following shows some simple examples of patch processing. See the
-[proc module](`dascore.proc`) for a list of all processing functions.
+[proc module](/reference/index.qmd#patch-processing) for a list of all processing functions.
-# Basic
+## Basic
There are several "basic" processing functions which manipulate the patch metadata, shape, etc. Many of these are covered in the [patch tutorial](patch.qmd), but here are a few that aren't:
-## Transpose
-The [`transpose` patch function](`dascore.Patch.transpose`) patch function simply transposes the dimensions of the patch, either by rotating the dimensions or to a new specified dimension.
+### Transpose
+The [`transpose` patch function](`dascore.proc.transpose`) patch function simply transposes the dimensions of the patch, either by rotating the dimensions or to a new specified dimension.
```{python}
import dascore as dc
@@ -26,8 +26,8 @@ print(f"dims after transpose: {transposed.dims}")
transposed = patch.transpose("time", "distance")
```
-## Squeeze
-[`squeeze`](`dascore.Patch.squeeze`) removes dimensions which have a single value (see also `numpy.squeeze`).
+### Squeeze
+[`squeeze`](`dascore.proc.squeeze`) removes dimensions which have a single value (see also `numpy.squeeze`).
```{python}
import dascore as dc
@@ -42,9 +42,9 @@ squeezed = flat_patch.squeeze()
print(f"Post-squeeze shape: {squeezed.shape}")
```
-## Dropna
+### Dropna
-The [`dropna` patch function](`dascore.Patch.dropna`) patch function drops "nullish" values from a given label.
+The [`dropna` patch function](`dascore.proc.dropna`) patch function drops "nullish" values from a given label.
```{python}
import numpy as np
@@ -66,11 +66,11 @@ assert not np.any(np.isnan(no_na_patch.data))
```
-# Decimate
+## Decimate
-[decimate](`dascore.Patch.decimate`) decimates a `Patch` along a given axis while by default performing low-pass filtering to avoid [aliasing](https://en.wikipedia.org/wiki/Aliasing).
+[decimate](`dascore.proc.decimate`) decimates a `Patch` along a given axis while by default performing low-pass filtering to avoid [aliasing](https://en.wikipedia.org/wiki/Aliasing).
-## Data creation
+### Data creation
First, we create a patch composed of two sine waves; one above the new
decimation frequency and one below.
@@ -87,7 +87,7 @@ patch = dc.examples.get_example_patch(
patch.viz.wiggle(show=True);
```
-## IIR filter
+### IIR filter
Next we decimate by 10x using IIR filter
@@ -99,7 +99,7 @@ decimated_iir.viz.wiggle(show=True);
Notice the lowpass filter removed the 200 Hz signal and only
the 10Hz wave remains.
-## FIR filter
+### FIR filter
Next we decimate by 10x using FIR filter.
@@ -108,7 +108,7 @@ decimated_fir = patch.decimate(time=10, filter_type='fir')
decimated_fir.viz.wiggle(show=True);
```
-## No Filter
+### No Filter
Next, we decimate without a filter to purposely induce aliasing.
@@ -117,9 +117,9 @@ decimated_no_filt = patch.decimate(time=10, filter_type=None)
decimated_no_filt.viz.wiggle(show=True);
```
-# Taper
+## Taper
-[taper](`dascore.Patch.taper`) is used to taper the edges of a patch dimension to zero. To see this, let's create a patch of all 1s and apply the taper function
+[taper](`dascore.proc.taper`) is used to taper the edges of a patch dimension to zero. To see this, let's create a patch of all 1s and apply the taper function
```{python}
import numpy as np
@@ -152,8 +152,8 @@ patch_ones.taper(distance=(None, 0.1)).viz.waterfall();
See also the [edge effects recipe](../recipes/edge_effects.qmd) for using tapering to help filtering.
-# Rolling
-The [rolling patch function](`dascore.Patch.rolling`) implements moving window operators similar to [pandas rolling](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html). It is useful for smoothing, calculating aggregated statistics, etc.
+## Rolling
+The [rolling patch function](`dascore.proc.rolling`) implements moving window operators similar to [pandas rolling](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html). It is useful for smoothing, calculating aggregated statistics, etc.
Here is an example of using a rolling mean to smooth along the time axis:
@@ -168,12 +168,12 @@ smoothed = patch.rolling(time=50*dt).mean()
smoothed.viz.waterfall();
```
-Notice the nan values at the start of the time axis. These can be trimmed with [`Patch.dropna`](`dascore.Patch.dropna`).
+Notice the nan values at the start of the time axis. These can be trimmed with [`Patch.dropna`](`dascore.proc.dropna`).
-# Whiten
+## Whiten
-The [`Patch.whiten`](`dascore.Patch.whiten`) function performs spectral whitening by balancing the amplitude spectra of the patch while leaving the phase (largely) unchanged. Spectral whitening is often a pre-processing step in ambient noise correlation workflows.
+The [`Patch.whiten`](`dascore.proc.whiten`) function performs spectral whitening by balancing the amplitude spectra of the patch while leaving the phase (largely) unchanged. Spectral whitening is often a pre-processing step in ambient noise correlation workflows.
To demonstrate, we create some plotting code and an example patch.
diff --git a/docs/tutorial/remote_patches.qmd b/tutorial/remote_patches.qmd
similarity index 100%
rename from docs/tutorial/remote_patches.qmd
rename to tutorial/remote_patches.qmd
diff --git a/docs/tutorial/spool.qmd b/tutorial/spool.qmd
similarity index 98%
rename from docs/tutorial/spool.qmd
rename to tutorial/spool.qmd
index f52ec0984..3f210d065 100644
--- a/docs/tutorial/spool.qmd
+++ b/tutorial/spool.qmd
@@ -8,11 +8,11 @@ Spools are containers/managers of [patches](patch.qmd). The spool interface is d
For file-backed spools, DASCore first scans metadata and coordinate summaries, then loads patch data only when you access a patch from the spool. This means methods like `get_contents()` work from patch summary metadata, while `spool[0]` or iteration materializes loaded patches.
-# Data Sources
+## Data Sources
The simplest way to get the appropriate spool for a specified input is to use the [`spool`](`dascore.spool`) function, which knows about many different input types and returns an appropriate [`BaseSpool`](`dascore.core.spool.BaseSpool`) subclass instance.
-## Patches (in-memory)
+### Patches (in-memory)
```{python}
import dascore as dc
@@ -22,7 +22,7 @@ patch_list = [dc.get_example_patch()]
spool1 = dc.spool(patch_list)
```
-## A Single file
+### A Single file
```{python}
import dascore as dc
@@ -42,7 +42,7 @@ dc.write(dc.get_example_patch(), remote_file, "DASDAE")
spool2_remote = dc.spool(remote_file)
```
-## A directory of DAS files
+### A directory of DAS files
```{python}
import dascore as dc
@@ -86,7 +86,7 @@ spool.update()
It is best not to delete files once added to a directory managed by DASCore.
:::
-### Path attributes (hive-style directories)
+#### Path attributes (hive-style directories)
Directory spools parse `key=value` pairs out of the paths inside the spool, in the style of [Hive partitioning](https://hive.apache.org/). Each directory segment can hold one pair (`acquisition_key=XX.R2D1..RAW/`), and any segment — including the file name, whose extension is ignored — can hold several separated by `__` (the same separator DASCore's default patch names use). The parsed values become string attributes: they show up in `get_contents()`, work with `select`, and are set on loaded patches. When a path attribute and an attribute stored inside the file share a name, the path wins — renaming a directory (or file) is how you attach or correct metadata without rewriting data.
@@ -139,7 +139,7 @@ from full reads. See the [Working with Remote Patches](remote_patches.qmd)
tutorial for the remote-cache policy, spool implications, and examples.
:::
-# Accessing patches
+## Accessing patches
Patches are extracted from the spool via simple iteration or indexing. New
spools are returned via slicing.
@@ -193,7 +193,7 @@ index_array = np.array([2, 0])
new = spool[index_array]
```
-# get_contents
+## get_contents
The [`get_contents`](`dascore.core.spool.BaseSpool.get_contents`) method returns a dataframe listing the spool contents. This method may not be supported on all spools, especially those interfacing with large remote resources.
@@ -215,7 +215,7 @@ display(contents.drop(columns=[c for c in contents.columns if c.startswith('_')]
The columns returned by `get_contents()` come from the same patch summary metadata exposed by `Patch.summary`, so fields such as `time_min`, `time_max`, and `distance_step` are available without loading the underlying patch data. Source metadata such as `source_path`, `source_format`, and `source_patch_id` are also available for file-backed spools.
-# select
+## select
The [select](`dascore.core.spool.BaseSpool.select`) method selects a subset of a spool and returns a new spool. [`get_contents`](`dascore.core.spool.BaseSpool.get_contents`) will now reflect a subset of the original data requested by the select operation.
@@ -242,7 +242,7 @@ subspool = spool.select(acquisition_key={'DAS2.R2D1..RAW', 'DAS3.R2D1..RAW'})
subspool = spool.select(tag='some*')
```
-# unselect
+## unselect
[`unselect`](`dascore.core.spool.BaseSpool.unselect`) is the complement of `select`: it returns the patches the same selection would have removed. Each keyword means what it means in `select`, so this is how to name what a spool does *not* want without spelling out the rest of the archive.
@@ -257,7 +257,7 @@ assert len(subspool) + len(spool.select(tag='some_tag')) == len(spool)
Coordinates are not accepted yet. Selecting on a coordinate trims each patch to the range as well as dropping the patches which miss it entirely, so the complement of `time=(t1, t2)` is the part of every patch *before* `t1` together with the part *after* `t2` — one patch becoming two. That is subdivision rather than filtering, and `unselect` does not do it yet; select the ranges to keep instead.
-# chunk
+## chunk
The [`chunk`](`dascore.core.spool.BaseSpool.chunk`) method controls how data are grouped together in patches within the spool. It can be used to merge contiguous patches together, specify the size of patches for processing, specify overlap with previous patches, etc.
@@ -287,7 +287,7 @@ size_chunked = spool.chunk(time=1 * megabytes)
A data size measures the data array only; coordinates, attrs, and any copies made later during processing are extra, so the patch as a whole is somewhat larger. The sample count is rounded down, so the patch's data never exceeds the requested size, and `MB` is 10^6^ bytes while `MiB` is 2^20^. `overlap` accepts the same forms.
-# concatenate
+## concatenate
Similar to `chunk`, [`Spool.concatenate`](`dascore.BaseSpool.concatenate`) is used to combine patches together. However, `concatenate` doesn't account for coordinate values along the concatenation axis, and can even be used to create new patch dimensions. Like `chunk`, it is available on every spool and produces a lazy, plan-backed result.
```python
@@ -306,7 +306,7 @@ merged = spool.concatenate(time=None)
print(merged[0].coords)
```
-# map
+## map
The [`map`](`dascore.core.spool.BaseSpool.map`) method applies a function to all patches in the spool. It provides an efficient way to process large datasets, especially when combined with clients (aka executors).
diff --git a/docs/tutorial/transformations.qmd b/tutorial/transformations.qmd
similarity index 97%
rename from docs/tutorial/transformations.qmd
rename to tutorial/transformations.qmd
index d81202c88..0955646e7 100644
--- a/docs/tutorial/transformations.qmd
+++ b/tutorial/transformations.qmd
@@ -6,7 +6,7 @@ execute:
In DASCore, transformations are operations which change the domain of a patch, and usually its units and dimension names along with it. Transforms can be found in the [transform module](`dascore.transform`) or accessed as `Patch` methods.
-# Discrete Fourier Transforms
+## Discrete Fourier Transforms
The [Discrete Fourier Transform](https://en.wikipedia.org/wiki/Discrete_Fourier_transform) (dft) is commonly used in many signal processing workflows. DASCore implements this as the [dft](`dascore.transform.fourier.dft`) patch method.
@@ -52,7 +52,7 @@ print(f"dims after round trip: {round_trip.dims}")
assert np.allclose(round_trip.data.real, patch.data)
```
-# Short Time Fourier Transform
+## Short Time Fourier Transform
Related to the Discrete Fourier Transform, the [Short Term Fourier Transform](https://en.wikipedia.org/wiki/Short-time_Fourier_transform) is useful for analyzing the time-dependent frequency content. DASCore implements this as [stft](`dascore.transform.fourier.stft`) and the corresponding [istft](`dascore.transform.fourier.istft`).
diff --git a/docs/tutorial/visualization.qmd b/tutorial/visualization.qmd
similarity index 91%
rename from docs/tutorial/visualization.qmd
rename to tutorial/visualization.qmd
index d320ea6b9..0b5dcc7be 100644
--- a/docs/tutorial/visualization.qmd
+++ b/tutorial/visualization.qmd
@@ -4,11 +4,11 @@ execute:
warning: false
---
-# Viz
+## Viz
The following provides some examples of patch visualization.
-See the [viz module documentation](`dascore.viz`) for a list of visualization functions
+See the [viz module documentation](/reference/index.qmd#visualization) for a list of visualization functions
-## Waterfall
+### Waterfall
The [`waterfall patch function`](`dascore.viz.waterfall`) creates a waterfall plot of the patch data.
```{python}
@@ -20,7 +20,7 @@ patch = dc.get_example_patch('example_event_2')
patch.viz.waterfall(show=True);
```
-### Controlling color scaling
+#### Controlling color scaling
The `scale` parameter controls the colorbar saturation. By default, waterfall uses a statistical fence (1.5×IQR) to exclude outliers and show the majority of the data clearly.
@@ -44,7 +44,7 @@ plt.tight_layout()
plt.show()
```
-## Wiggle
+### Wiggle
The [`wiggle patch function`](`dascore.viz.wiggle`) creates a wiggle plot of the patch data. We'll use the same patch as above to model this function.
```{python}
diff --git a/utilities/index.qmd b/utilities/index.qmd
new file mode 100644
index 000000000..8dc218c57
--- /dev/null
+++ b/utilities/index.qmd
@@ -0,0 +1,16 @@
+---
+title: Utilities
+description: "Every public helper in dascore.utils, with details on demand."
+---
+
+DASCore's `utils` package holds the helpers the rest of the library is built from. They are public, but they are supporting cast rather than the main interface — the [API reference](/reference/index.qmd) covers the patch, spool and processing functions most users need.
+
+Everything defined in `dascore.utils` is listed below, one section per module. Each entry shows its signature and summary; open **Details** for parameters, returns and examples.
+
+```{python}
+#| echo: false
+#| output: asis
+from dascore.utils.docs import render_package_api
+
+print(render_package_api("dascore.utils"))
+```