Skip to content

Treat patch attrs and coords as fully independent - #757

Merged
d-chambers merged 3 commits into
devfrom
attr-coord-decoupling
Jul 19, 2026
Merged

Treat patch attrs and coords as fully independent#757
d-chambers merged 3 commits into
devfrom
attr-coord-decoupling

Conversation

@d-chambers

@d-chambers d-chambers commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Description

This PR makes patch attrs and coords fully independent: no code path infers coordinate structure from attr names anymore, and no attr name is reserved or rejected because it happens to look like coordinate metadata. It is a hard break on the dev line.

The problem (found via #656): any attr whose name ended in a CoordSummary field (_min, _max, _step, _len, _dims, _fingerprint) was silently captured as coordinate metadata for a phantom coordinate named after the prefix. separate_coord_info({'channel_step': 3}) invented a channel coordinate, and passing the real dims did not protect callers because greedy inference overrode them. This produced four different behaviors for the same attr depending on the entry point (raise, silently accept, silently accept, warn-and-drop), silently dropped legitimate vendor metadata like Sintela's channel_step from the spool index, and could crash get_coord_manager outright (get_coord(step=3) cannot build a coordinate from one field). The Sintela reader had to rename its attr to channel_stride to dodge this.

The rule now: attrs are an opaque, open namespace; coords are structural and live only in CoordManager. update_attrs(channel_step=3) sets an attr, always, on every entry point, even when the patch has a channel coordinate. Nothing is inferred from a name's shape. There are no runtime checks, with exactly one exception described below.

What was deleted:

  • separate_coord_info and its greedy suffix/dims inference, _raise_if_coord_attr_updates (so update_attrs no longer raises PatchAttributeError for coord-shaped names), and is_valid_coord_str.
  • The attrs branch of get_coord_manager (the crash path) and CoordManager.update_from_attrs (zero production callers).
  • The catalog-wide envelope-name reservation in index ingest (_is_envelope_shaped) and its warn-and-drop; every scalar attr is now indexed and queryable via the _attrs namespace.

What replaced it:

  • A dasdae-private _compat module holding all legacy DASDAE metadata handling. Legacy files (which mixed flat coord keys into the stored attr namespace) are cleaned by stripping exact {coord}_{field} keys against the file's actually-stored coord names — never by key shape, so an old file with a pulse_len attr and no pulse coord keeps its attr. New files carry a root marker (__attrs_coords_separate__) declaring the attr namespace pure, so even an attr shadowing the patch's own coord envelope (distance_step alongside a distance coord) round-trips exactly. An import-graph test pins _compat inside dascore/io/dasdae/.
  • One surviving check: when the flat get_contents() frame is materialized, an attr whose name equals a coordinate envelope column ({coord}_min/max/step for a coord present in that frame) is a genuine collision — the coord wins the bare column, the attr column is omitted, a UserWarning fires once per backend, and the attr stays fully queryable via _attrs. The check runs after coord envelope pivoting so cross-patch collisions (attr from one patch vs coord from another) are caught too.
  • A parametrized hygiene test over every FiberIO in the common read suite asserting no shipped reader mirrors coord metadata into attrs (no attr named {coord}_{field} for a coord on the same patch). This immediately caught the TDMS reader doing exactly that; fixed here.
  • Regression tests for every failure mode of the old behavior: the nine-name suffix-capture sweep across all entry points, the dims-override case, the get_coord_manager crash repro (now a TypeError), DASDAE round-trips including the self-shadowing case, index ingest without warnings, and the collision warning with cross-patch and negative-control variants.

Follow-up (separate PR, after this lands): rebase #656 and revert Sintela's channel_stride back to the vendor name channel_step, which this PR makes representable end to end.

Changelog

  • changed breaking: attr names that look like flat coordinate metadata (channel_step, gain_max, …) are ordinary attrs on every entry point; coordinates are never inferred from attr names, and dascore.utils.attrs.separate_coord_info and CoordManager.update_from_attrs are removed.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • New Features
    • Coordinate-shaped attribute names (e.g., time_step, channel_min/max/step) are handled as regular patch attributes (not used to infer/update coordinates).
    • Coordinate updates now use flat {dim}_{field} kwargs.
    • Legacy DASDAE formats are detected and normalized automatically on read.
  • Bug Fixes
    • Prevented coord metadata from being mirrored into patch attributes during scanning and DASDAE persistence.
    • Index/spool flat views now resolve name collisions so coordinate envelope columns take precedence; clashing attributes remain available via _attrs.
    • Updated ingest/reservation rules for fixed time/distance envelope columns.
  • Documentation
    • Clarified collision behavior for spool/flat relations and _attrs access.

Attrs and coords no longer interact anywhere in DASCore. Attr names that
look like flat coordinate metadata (channel_step, pulse_len, gain_max, ...)
are ordinary attrs on every entry point: PatchAttrs construction, subclass
fields, update_attrs, DASDAE round-trips, and the spool index. Coordinates
are never inferred from attr names.

- Delete separate_coord_info and its greedy suffix/dims inference, the
  _raise_if_coord_attr_updates guard, and is_valid_coord_str.
- Delete the attrs branch of get_coord_manager (a coord-shaped attr could
  crash it via a phantom single-field coord) and the unused
  CoordManager.update_from_attrs.
- Quarantine all legacy DASDAE metadata handling in a dasdae-private
  _compat module; legacy flat coord keys are stripped against the file's
  actual stored coord names, never by key shape. New files carry a root
  marker declaring the attr namespace pure so coord-shaped attrs
  round-trip exactly.
- Index every scalar attr; drop the catalog-wide envelope-name
  reservation and its warn-and-drop. The one surviving check: when the
  flat get_contents frame is built, an attr whose name equals a
  coordinate envelope column warns once and the coord wins the column;
  the attr stays queryable via the _attrs namespace.
- Stop the TDMS reader from mirroring coord metadata into attrs (caught
  by the new FiberIO hygiene test).
- Add regression tests for every failure mode of the old suffix-capture
  behavior, a parametrized hygiene test over all shipped readers, and an
  import-graph test pinning _compat inside dascore/io/dasdae.
@d-chambers d-chambers added the ready_for_review PR is ready for review label Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 659022ca-3a76-4fbc-abce-9152a1f7c0ab

📥 Commits

Reviewing files that changed from the base of the PR and between 508eb3b and aede734.

📒 Files selected for processing (10)
  • dascore/io/dasdae/_compat.py
  • dascore/io/dasdae/core.py
  • dascore/io/dasdae/utils.py
  • dascore/io/index/backend.py
  • dascore/io/index/planned.py
  • dascore/io/index/query.py
  • dascore/io/index/schema.py
  • tests/test_core/test_coordmanager.py
  • tests/test_integrations/test_attr_coord_independence.py
  • tests/test_io/test_dasdae/test_dasdae.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • dascore/io/dasdae/core.py
  • dascore/io/index/backend.py
  • dascore/io/dasdae/utils.py
  • tests/test_core/test_coordmanager.py

📝 Walkthrough

Walkthrough

Coordinate-shaped attributes remain independent from coordinate construction, DASDAE legacy metadata is normalized during reads, and index flat views now resolve attribute/envelope collisions in favor of coordinate columns while retaining _attrs access.

Changes

Coordinate metadata independence

Layer / File(s) Summary
Attribute and coordinate update contract
dascore/core/attrs.py, dascore/core/coordmanager.py, dascore/core/coords.py, dascore/proc/basic.py, dascore/utils/*, tests/test_core/*, tests/test_utils/*
Coordinate-shaped keys are accepted as ordinary attributes, coordinate updates use flat kwargs, and legacy attribute-based coordinate-manager APIs and helpers are removed.
DASDAE legacy metadata handling
dascore/io/dasdae/*, dascore/io/tdms/utils.py, tests/test_io/test_dasdae/*, tests/test_io/test_common_io.py, tests/test_integrations/test_attr_coord_independence.py
DASDAE files identify separated attributes, legacy coordinate metadata is translated and stripped during reading, and readers no longer mirror coordinate metadata into attrs.
Index collision materialization
dascore/io/index/*, docs/notes/spool_index.qmd, tests/test_io/test_index/*, tests/test_integrations/test_attr_coord_independence.py
Coordinate-envelope columns are pivoted before dynamic attributes; collisions emit warnings, preserve the envelope in flat output, and retain the attribute in _attrs queries.

Possibly related PRs

  • DASDAE/dascore#527: Changes related to PatchAttrs.update and coordinate-shaped attribute handling.
  • DASDAE/dascore#751: Introduces the spool-index pipeline extended here with collision-aware flat and _attrs handling.

Suggested labels: patch, IO, spool

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: patch attrs and coords are made independent.
Description check ✅ Passed The description follows the required template with a clear problem statement, scope, issue reference, and checklist.
Docstring Coverage ✅ Passed Docstring coverage is 95.83% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch attr-coord-decoupling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added IO Work for reading/writing different formats patch related to Patch class labels Jul 19, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24ef94fad0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +49 to +55
has_legacy_patches = (
waveforms is not None
and len(waveforms)
and not hfile.attrs.get(_SEPARATE_ATTRS_KEY, False)
)
if not has_legacy_patches:
hfile.attrs[_SEPARATE_ATTRS_KEY] = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve new attrs when appending to legacy DASDAE files

When H5Writer appends to an existing unmarked legacy file, this leaves the file-wide marker unset, but the newly written groups already use the separated format. Subsequent reads therefore treat those new groups as legacy and strip_legacy_coord_fields removes legitimate attrs such as time_step whenever the group has a matching coordinate, breaking the promised round trip. The format distinction needs to be recorded per group, or new groups in mixed files must otherwise be exempted from legacy stripping.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aede734. The separated-attrs marker is now stamped on each patch group as well as the file root, and the read/scan paths check _is_legacy_group (file legacy AND group unmarked). Groups appended to a legacy file keep exact attr round-trips while the pre-existing groups still get legacy stripping; test_append_to_legacy_file_keeps_new_attrs covers the mixed-file case end to end.

Comment thread dascore/io/index/backend.py Outdated
Comment on lines 688 to 691
df = self._pivot_coords(df)
df = self._apply_attr_columns(df, attr_columns)
if residuals:
df = apply_residuals(df, residuals)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply regex residuals to the original attr values

When an explicitly namespaced attr collides with a coordinate envelope and is queried with a compiled regex, _apply_attr_columns omits the attr before apply_residuals runs. The residual then reads the coordinate-backed flat column of the same name rather than the attr value, so _attrs={"channel_min": re.compile(...)} can incorrectly return no patches or match based on coordinate metadata even though the comment says the attr remains queryable through _attrs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aede734. apply_residuals now takes the original attr series and prefers them over same-named flat columns. Worth noting the scenario is narrower than described: the SQL attr clause pre-filters to rows that have the attr, so a cross-patch collision disappears from the filtered frame — the bug only bit when a single patch carried both the coord and the same-named attr. That case is pinned by test_regex_query_reads_attr_under_collision (verified failing against the old behavior).

omitted from the flat view (still queryable via the _attrs
namespace) with a warning.
"""
clobbered = frozenset(name for name in new_columns if name in out.columns)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect actual time and distance coordinate collisions

For patches without a time or distance coordinate, attrs such as time_step should be ordinary flat attrs, but the patches table always projects nullable time_* and distance_* columns. Checking only name in out.columns therefore classifies these attrs as collisions, warns, and suppresses their values even though no matching coordinate exists in the result. Collision detection should consult the pivoted coordinate membership rather than mere presence of the fixed schema columns.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aede734, via reservation rather than membership detection: time_min/max/step and distance_min/max/step are now in RESERVED_ATTR_COLUMNS, so attrs with those names warn and are skipped at ingest like other structural storage columns. These six exist on the patches table for every row regardless of the patch's coords, and letting an attr fill them would make downstream consumers (e.g. plan output records) misread attr values as structural envelopes. Every other coord's envelope columns only exist when the coord does, so name in out.columns is a true membership test there and the collision-warning path is unchanged. test_fixed_envelope_attr_warns_and_skips covers it.

Comment on lines +208 to +212
# Structural storage/contract column names cannot double as attr
# columns. Coordinate-envelope-shaped names (e.g. "channel_step")
# are ordinary attrs; a genuine collision with a coord envelope
# column is handled (warn, coord wins) when the flat view is built.
if sanitize_attr_name(name) in RESERVED_ATTR_COLUMNS:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve envelope-shaped attrs in derived spool records

Allowing names such as channel_step into the index exposes an incompatible derived-catalog path: dascore/io/index/planned.py::_output_records still discards every public key ending in _min, _max, _step, or _units, regardless of whether that key belongs to a coordinate. Consequently, after operations that create a derived spool, such as spool.chunk(time=...) or concatenation, an ordinary channel_step attr with no channel coordinate disappears from get_contents() and is no longer selectable through _attrs; the derived-record filter must distinguish actual coordinate envelopes from ordinary attrs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aede734. _output_records now skips only envelope keys of coords actually present in the row (dims, aux coords, and _{name}_def_key-derived names, plus the fixed time/distance columns) instead of anything suffix-shaped, so a channel_step attr with no channel coordinate survives chunk/concat into the derived catalog. test_chunk_preserves_coord_shaped_attr covers get_contents and _attrs selection after spool.chunk.

@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (2aa3c00) to head (aede734).

Additional details and impacted files
@@            Coverage Diff            @@
##               dev      #757   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          161       162    +1     
  Lines        16619     16544   -75     
=========================================
- Hits         16619     16544   -75     
Flag Coverage Δ
network 49.61% <68.99%> (-0.06%) ⬇️
unittests 99.98% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_core/test_coordmanager.py (1)

912-931: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Swap test names to match their behavior.

The test names and docstrings for updating min and max are swapped relative to their implementations. test_update_min tests the {dim}_max kwarg, while test_update_max tests the {dim}_min kwarg.

Please swap their names (and docstrings) to reflect what is actually being tested.

♻️ Proposed fix
-    def test_update_min(self, cm_basic):
-        """Ensure a {dim}_max kwarg updates the appropriate coord."""
+    def test_update_max(self, cm_basic):
+        """Ensure a {dim}_max kwarg updates the appropriate coord."""
         for dim in cm_basic.dims:
             coord = cm_basic.coord_map[dim]
             new = cm_basic.update(**{f"{dim}_max": coord.min()})
             new_coord = new.coord_map[dim]
             assert len(new_coord) == len(coord)
             assert new_coord.max() == coord.min()
 
-    def test_update_max(self, cm_basic):
-        """Ensure a {dim}_min kwarg updates the appropriate coord."""
+    def test_update_min(self, cm_basic):
+        """Ensure a {dim}_min kwarg updates the appropriate coord."""
         for dim in cm_basic.dims:
             coord = cm_basic.coord_map[dim]
             dist = coord.max() - coord.min()
             new = cm_basic.update(**{f"{dim}_min": coord.max()})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_core/test_coordmanager.py` around lines 912 - 931, Swap the names
and docstrings of test_update_min and test_update_max so each accurately
describes its tested keyword: the `{dim}_max` behavior belongs to
test_update_max, and the `{dim}_min` behavior belongs to test_update_min. Leave
the test implementations unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dascore/io/dasdae/_compat.py`:
- Around line 53-78: In the legacy string-coordinate handling block, check
get_config().allow_dasdae_format_unpickle before calling pickle.loads, and skip
deserialization entirely when disabled; preserve decoding only for explicitly
enabled compatibility. Update
test_translate_legacy_attrs_ignores_non_mapping_coords to assert that
pickle.loads is not called under the default configuration.

In `@dascore/io/index/backend.py`:
- Around line 852-881: Update the collision warning in _apply_attr_columns to
pass stacklevel=2, matching the warning behavior in _ensure_attr_columns so it
points to the caller rather than the internal frame.

---

Nitpick comments:
In `@tests/test_core/test_coordmanager.py`:
- Around line 912-931: Swap the names and docstrings of test_update_min and
test_update_max so each accurately describes its tested keyword: the `{dim}_max`
behavior belongs to test_update_max, and the `{dim}_min` behavior belongs to
test_update_min. Leave the test implementations unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5bdc9e67-e642-42b2-b9d5-3bd8d2af2dc8

📥 Commits

Reviewing files that changed from the base of the PR and between 2aa3c00 and 24ef94f.

📒 Files selected for processing (21)
  • dascore/core/attrs.py
  • dascore/core/coordmanager.py
  • dascore/io/dasdae/_compat.py
  • dascore/io/dasdae/core.py
  • dascore/io/dasdae/utils.py
  • dascore/io/index/backend.py
  • dascore/io/index/ingest.py
  • dascore/io/tdms/utils.py
  • dascore/proc/basic.py
  • dascore/utils/attrs.py
  • dascore/utils/misc.py
  • docs/notes/spool_index.qmd
  • tests/test_core/test_attrs.py
  • tests/test_core/test_coordmanager.py
  • tests/test_core/test_patch.py
  • tests/test_integrations/test_attr_coord_independence.py
  • tests/test_io/test_common_io.py
  • tests/test_io/test_dasdae/test_dasdae.py
  • tests/test_io/test_index/test_index_edge_cases.py
  • tests/test_utils/test_attrs_utils.py
  • tests/test_utils/test_coordmanager_utils.py
💤 Files with no reviewable changes (4)
  • dascore/io/tdms/utils.py
  • dascore/core/attrs.py
  • dascore/proc/basic.py
  • dascore/utils/misc.py

Comment thread dascore/io/dasdae/_compat.py Outdated
Comment thread dascore/io/index/backend.py
Its only production caller was the TDMS coord-metadata mirroring removed
in the previous commit; the method existed solely to produce flat
{name}_{field} attr mirrors, which no longer have any consumer.
@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation proc Related to processing module labels Jul 19, 2026
@github-actions

github-actions Bot commented Jul 19, 2026

Copy link
Copy Markdown

✅ Documentation built:
👉 Download
Note: You must be logged in to github and a DASDAE member to access the link.

- Check the unpickle opt-in gate before pickle.loads ever runs; a
  malicious legacy coords payload previously executed during the decode
  that preceded the gate. Regression test proves no decode happens
  without opt-in.
- Stamp the separated-attrs marker per patch group as well as per file,
  so groups appended to a legacy file are not legacy-stripped on read
  (their coord-shaped attrs now round-trip exactly).
- Reserve the six fixed time/distance envelope columns at ingest: they
  exist on the patches table for every row regardless of coords, so a
  same-named attr could not be told apart from structural metadata
  downstream. Other coords' envelopes keep the collision-warning path.
- Preserve envelope-shaped attrs through derived catalogs: plan output
  records now skip only envelope keys of coords actually present in the
  row instead of anything suffix-shaped, so chunk/concat keep attrs
  like channel_step queryable.
- Evaluate regex attr residuals against the original attr values, which
  the flat column no longer holds when a patch has both a coord and a
  same-named attr.
- Initialize the collision warn-once set in the backend constructor,
  add stacklevel to the warning, share the legacy flat-field constant,
  and unswap two coordmanager test names.
@coderabbitai coderabbitai Bot added spool related to Spool class and removed proc Related to processing module documentation Improvements or additions to documentation labels Jul 19, 2026
@d-chambers
d-chambers merged commit a063147 into dev Jul 19, 2026
30 checks passed
@d-chambers
d-chambers deleted the attr-coord-decoupling branch July 19, 2026 17:57
d-chambers added a commit that referenced this pull request Jul 19, 2026
Attrs and coords are independent as of #757, so the channel_stride stopgap
is no longer needed; the attr keeps the vendor's channel_step name and a
test pins that it survives indexing.

Review fixes:
- Discard unknown fields after scan-mode parsing. Omitting the sample
  declarations stopped them being decoded but protobuf still retained their
  raw bytes, so a metadata-only scan held ~99% of the recording in memory.
- Label complex FFT packets fourier_transform rather than
  power_spectral_density; complex coefficients are not real power.
- Compare sample_count modulo 2**32 so a uint32 rollover mid-acquisition is
  not misread as a gap.
- Raise a specific error for timeseries packets carrying samples in
  raw_frames instead of a confusing payload-size mismatch.
- Restore dascore.io.sintela_binary as a forwarding module; it shipped in
  v0.1.18 and the move would have broken existing imports.
@d-chambers d-chambers removed the ready_for_review PR is ready for review label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

IO Work for reading/writing different formats patch related to Patch class spool related to Spool class

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant