Coordinate Guidelines
These rules govern DASCore’s coordinates. If code disagrees with them, fix the code, or change this note first in its own pull request and log why.
The model
DASCore separates two jobs:
- Assembly (
select,chunk,concatenate) decides which samples belong together and keeps labels as recorded;chunkmay only shift a whole piece by up tomax_shift. - Regularization (
snap_coords,fill_gaps, on patches and spools) makes a coordinate evenly sampled. Only it moves individual labels or adds samples, and only when asked.
Joining across a gap and fixing it are two visible steps:
joined = spool.chunk(time=None, tolerance=10) # seams up to 10 steps (9 missing samples) stay holes
filled = joined.fill_gaps(time=10) # insert the missing samples, or
snapped = joined.snap_coords("time") # spread the samples evenlytime is only an example; no dimension name is special.
Scope
The sampling rules apply to one-dimensional, monotonic, numeric or time-like (datetime or timedelta) dimension coordinates; others have no step.
Associated coordinates run along a dimension without being one. Whatever an operation does along a dimension, it does to them; if it can’t, it drops them with a warning (fill_gaps gives them NaN or NaT where it can). Selecting by a multi-dimensional one raises and suggests a mask. Units always survive.
Terms
- Step: the spacing the labels follow. Integer and datetime steps are exact fractions of a tick, so rates like 1024 Hz are exact (labels are grid values rounded down to whole ticks).
- Grid: a step plus an origin.
- Offset: how far a label sits from its grid position.
- Hole (gap): a grid position with no sample.
- Seam: where one piece or run ends and the next begins.
A coordinate is described by its step (None if it has none) and two independent flags:
| No holes | Holes (gapped) |
|
|---|---|---|
| On the grid | evenly sampled | gapped |
Offsets under half a step (jittered) |
jittered | jittered and gapped |
coord.evenly_sampled means a step, not jittered and not gapped. A coordinate with no step is irregular.
Rules
Labels
Labels are what was recorded. Only
snap_coords,fill_gaps, a reader’ssnap(rule 3) andchunk’s piece shift (rule 6) change them; storing a label as a grid value within float precision doesn’t count. Removing samples keeps the step, leaving holes; only an explicit stride gives a coarser step.Inference finds a grid or nothing.
get_coord(data=...)checks every label against a fitted grid, not just the spacings, so drift can’t hide: all on the grid at consecutive positions is evenly sampled; all offsets and spacing errors under half a step, with no repeats, is jittered; anything else has no step. Inference never invents holes. With a stated step, labels are placed on that grid, skipped positions become holes, and two labels on one position raise. Integer and datetime grids are found exactly in their own tick unit; floats are judged in their stored dtype.Readers snap the coordinates
snapnames.dc.read,dc.scan,dc.spooland every reader takesnap:None(default): the format’s default names, shown in its reader’s signature;True: every one-dimensional numeric or time-like dimension;False: nothing; every coordinate exactly as stored;- a tuple such as
("time",): those names; a missing or unsnappable name raises.
A snapped coordinate is assumed to have no holes inside a file or patch; gaps show up between files, where
chunksees them. One shared helper builds every snapped grid, from a reliable stated start and acquisition rate, else from the first and last stored labels and the count, so interrogators with unlocked clocks still come back evenly sampled. It checks the grid against stored labels (the ends, plus a few interior ones for end-point grids) and warns, naming the file, beyondmax_shift. The index records itssnapand rescans when opened with another. A conformance test holds every format to this rule.Operations check what they need. FFTs, filters and other operations assuming contiguous samples use
get_coord(dim, require_evenly_sampled=True), and the error namesfill_gapsandsnap_coords.coord.stepis set whenever there is a step, jittered or not.
Joining
Tolerance is the widest seam
chunkmay join, in steps or, as a quantity or timedelta, a distance; a seam skippingmpositions spansm + 1steps. Pieces with no step join only under a distance. How far labels move ismax_shift, nevertolerance.chunkassembles. It joins compatible pieces (notes/patch_compatibility.qmd) along the coordinate’s direction, judging each seam from the recorded labels on either side.- Gaps. Missing samples within
tolerancejoin and stay holes; wider gaps split, inside pieces too. - Offsets. A piece off the output’s grid by at most
max_shiftis shifted onto it as a block; further, but under half a step, it keeps its labels and the output is jittered.max_shift=0shifts nothing. After a file ending at 99 s with a 1 s step, a next file starting at 100.1 s is shifted, one at 100.3 s is jittered, and one at 103 s leaves a three-sample hole. - Rates. The grid is refitted over the whole output; a piece that drifts half a step or more from it (a slightly different rate) starts a new output.
- A single sample takes its neighbours’ step; pieces with no step join by recorded spacing.
- Overlaps. The earlier piece wins; later samples fill only its holes.
chunk_planrecords per piece why each output starts, the shift, and how many samples were dropped.
- Gaps. Missing samples within
concatenatejoins exactly, in order, keeping every label; it never shifts, splits or fills. Different steps give no step; overlapping labels give an unordered coordinate and a warning.
Selecting and windows
- Ranges and windows.
- Picking by value (
select,sel, ranges, annotations) includes both ends; positions (isel,samples=True) and windows exclude the end, as in xarray. Reversed bounds are swapped; on a descending coordinate, start and end mean the lower and upper values. Spool.selectandchunkaccept ranges,(n, 2)arrays of ranges, or annotation sets.Spool.selectreturns each source’s matching part, unjoined;chunkjoins each range into one output, or labelled fragments if it splits. Arrays on several dimensions pair row by row;join_dim(default: the first) names the joining one. OnPatch.selectan array still lists values to pick, as released.chunk(dim=length, overlap=o)starts windows atstart + k * (length - o)exactly, so counts may differ by one; negative overlap skips;samples=Truecounts samples. Jittered coordinates are windowed by grid position.
- Picking by value (
- Completeness applies to
chunkonly. An output is complete when both edge positions hold samples; interior holes are judged bytolerance.on_incomplete("drop","keep","warn","raise") handles the rest, defaulting to"drop"for windows and"raise"for ranges. Outputs with no step are kept;selectjust clips.
Regularizing
snap_coordsandfill_gaps.snap_coords(*dims)works as released: sort, keep the end labels and the count, spread samples evenly; evenly sampled coordinates are left alone.max_shift=raises on larger moves, and moves over one step warn, since a hole is being spread.step=places labels on that grid instead, keeping holes.fill_gaps(**{dim: tolerance})fills holes up to that size (all, if none given) withfill_value(NaN), without moving labels. Jittered or stepless coordinates raise, pointing tosnap_coords(step=...), as does afill_valuethe dtype can’t hold.- Without names they apply to every eligible dimension. On spools they are lazy and per patch, using the index where possible and otherwise reading only the coordinate.
Reports and storage
Reports match
chunk.get_gaps(tolerance=t)lists the splits and kept holes ofchunk(tolerance=t), with span and missing-sample count.get_coveragecounts missing samples (unknown without a step).get_contentsadds{dim}_jitteredand{dim}_gappedbeside{dim}_step, filterable withselect.The index mirrors the coordinate: one row per patch, a coordinate table (step, flags, envelope, offset bounds) and a run table matching the in-memory runs, which never contain holes. Gap logic is written once over runs, so memory and index agree. Undecidable cases read the coordinate lazily. Beyond
max_index_runs, a coordinate is stored as an envelope with a warning naming the file and the fix.Writers keep steps and holes, or refuse, saying to regularize or split first. They never silently split, stretch, fill or drop a step.
Constants
These are the only thresholds.
| Name | Value | Use |
|---|---|---|
dascore.config.max_shift |
0.2 steps | Default for chunk and the reader warning: above measured jitter (0.17), below a missing sample’s effect. snap_coords has no default limit. |
DEFAULT_TOLERANCE |
1.5 steps | Default tolerance: only contiguous pieces join. |
dascore.config.max_index_runs |
256 | Runs stored per coordinate in the index. |
| Float exactness | max(_GRID_RTOL × |step|, 4 ulp) |
In the stored dtype; float64 holding float32 values counts as float32. |
| Fractional steps | ≥ 100 ticks | So an integer array missing one value isn’t read as a fractional grid. |
Methods that change coordinates
| Job | Patch | Spool | Notes |
|---|---|---|---|
| Pick | select, sel, isel, unselect |
select, unselect |
select for compatibility, sel/isel for xarray users. |
| Reorder | order, sort_coords |
sort |
|
| Change coordinates | update_coords |
All changes go through it, including coords_from_df, add_distance_to, enrich. |
|
| Rebuild | replace |
Everything at once; update and new remain. |
|
| Remove, rename | drop_coords, rename_coords |
drop_coords(to_attrs=True) replaces squeeze_coords. |
|
| Assemble | chunk, concatenate |
||
| Split at holes | split_gaps |
dc.spool([patch]) no longer splits. |
|
| Regularize | snap_coords, fill_gaps |
snap_coords, fill_gaps |
|
| Sum patches | stack |
Along dim_vary, keeps the first patch’s labels. |
Processing that changes a grid (resample, decimate, dft) states the step it produces.
Deprecations and breaks
Released behaviour is deprecated with a warning for at least one release rather than changed outright, unless there is a compelling reason, which the decisions log records. Unreleased features (Spool.cut, squeeze_coords, sampling_group_tolerance, half-open annotation ranges) change freely.
| In v0.1.24 | Deprecation |
|---|---|
chunk snaps outputs, stretching across gaps and rates |
Kept one release, warning where the new rules differ; then holes are kept |
chunk(snap_coords=...) |
Warns; True keeps the released result, False means max_shift=0 |
chunk(keep_partial=True) |
Warns; means on_incomplete="keep" |
get_coord(data=...) snapping within 0.1% |
Warns where rule 2 differs, then switches |
order dropping missing values |
Warns, then raises |
coords_from_df overwriting coordinates |
Warns, then refuses |
concatenate_patches, merge_patches |
Warn, then are removed in 0.2 |
Afterwards, gapped chunk outputs refuse filters until regularized, reading warns when stored times disagree with the grid, Sentek time (which repeats) is unordered, and writing holes to formats that can’t store them raises.
Future work
Coordinates will hold a mapping of keys to any array-like, real or virtual, carried through snap_coords and similar so recorded labels survive. Not yet designed.
Decisions log
- 2026-10-04: Assembly and regularization are separate; a design with join modes proved too complex.
- 2026-10-04: An explicit
tolerancejoins across gaps, and patches may hold holes, reversing those parts of #1236. - 2026-10-05: The index returns to a run table, reversing #1284, so memory and index share one model.
- 2026-10-05:
snap_coordskeeps its released behaviour, addingmax_shift(after silent large moves in #803, #896, #1418) andstep. - 2026-10-05: Boundaries follow xarray: picks include both ends, positions exclude the end.
- 2026-10-06: Selection keeps the step (a draft that coarsened it erased real holes); completeness checks edge positions (a draft using
tolerancethere let truncated windows pass). - 2026-10-06: A prototype on ~5,800 Febus files set
max_shiftto 0.2 (half a step let a missing sample snap) and seams by recorded labels (projecting from the first piece dropped real samples). Filed #1419, #1420. - 2026-10-07: Readers assume no holes inside a file (clocks may be unlocked from sampling);
snap=Nonemeans the format’s defaults. - 2026-10-07: Jitter and holes are independent flags, replacing a level ladder;
require_evenly_sampledstays. - 2026-10-07:
max_shift=0replaces asnap_jitterflag. - 2026-10-07: Released behaviour is deprecated rather than changed outright, unless there is a compelling reason.