AlignmentBundle
AlignmentBundle(
id='',
name=None,
timelines=dict(),
groups=dict(),
timeline_to_group=dict(),
meta=dict(),
cross_group_claims=list(),
_uid_to_timeline_id=dict(),
_timeline_id_to_uid=dict(),
_warp_map_cache=dict(),
_matchline_cache=dict(),
_matchgraph_cache=dict(),
_cache_claims_hash=0,
)The primary entry point for all alignment workflows.
An AlignmentBundle manages timelines and their alignment relationships. Within a group, coordinate transfer uses linear interpolation (TimelineGroup.convert()). Across groups, transfer is mediated by the MatchClaim → MatchLine → WarpMap pipeline.
The bundle provides:
- Timeline registration and lookup
- Group management (collections of perfectly aligned timelines)
- Coordinate transfer between any two timelines (same-group or cross-group via
MatchClaim/WarpMap)
IMPORTANT: The resulting bundle structure is order-independent. Adding timelines in any order produces the same alignment relationships and coordinate transfer results.
Attributes: id: Unique identifier for this bundle. name: Optional human-readable name. timelines: Dictionary mapping bundle UIDs to Timeline objects. groups: Dictionary mapping group IDs to TimelineGroup objects. timeline_to_group: Mapping from bundle UID to its containing group ID. cross_group_claims: MatchClaims connecting timelines across groups.
Note: The bundle maintains a UID mapping layer. Users interact with bundle UIDs (e.g., “tl1”, “tl2”), while groups internally use the actual timeline.id. The bundle translates between these two namespaces transparently.
Examples: Basic usage with explicit timelines:
>>> bundle = AlignmentBundle()
>>> bundle.add_timeline(score_timeline, uid="score")
>>> bundle.add_timeline(audio_timeline, uid="audio", aligned_to="score")
>>> bundle.transfer(100.0, "score", "audio")
45.5
Cross-group transfer via MatchClaims:
>>> bundle.add_match_claims(match_results.match_claims)
>>> bundle.transfer(79.0, "clt1", "dpt1") # score -> audio
1234567.0
Attributes
| Name | Description |
|---|---|
| default_group | Get the single group or primary group. |
| group_ids | List of all group IDs in the bundle. |
| n_groups | Number of groups in this bundle. |
| n_timelines | Number of timelines in this bundle. |
| timeline_ids | List of all timeline IDs in the bundle. |
Methods
| Name | Description |
|---|---|
| add_group | Add a pre-built TimelineGroup with all its timelines at once. |
| add_match_claims | Add MatchClaims connecting timelines across different groups. |
| add_timeline | Add a timeline, optionally aligned to an existing timeline. |
| are_commensurable | Check if two timelines can be connected via transfer. |
| create_match_claims | Create MatchClaims from a list of event pairs. |
| diagram | Generate ASCII diagram for this bundle. |
| get_group | Get a group by ID. |
| get_group_for_timeline | Get the group containing a timeline. |
| get_match_claims | Query MatchClaims connecting timelines across groups. |
| get_matchstamp_at | Get a cross-group MatchStamp at a coordinate on a timeline. |
| get_matchstamp_table | Get a PyArrow table of MatchStamps for alignment queries. |
| get_matchstamps | Get MatchStamps for a list of MatchClaims. |
| get_timeline | Get a timeline by ID. |
| get_timelines | Get multiple timelines by their IDs. |
| get_timestamp_at | Get a cross-group timestamp at a coordinate on a given timeline. |
| summary | Get a summary of the bundle contents. |
| transfer | Transfer a coordinate from one timeline to another. |
| transfer_interval | Transfer an interval from one timeline to another. |
add_group
AlignmentBundle.add_group(group, *, uid_map=None)Add a pre-built TimelineGroup with all its timelines at once.
This is the bulk-registration counterpart of add_timeline(..., as_group=...). It registers the group and every timeline it already contains into the bundle’s bookkeeping (UID mapping, timeline registry, group registry).
Args: group: A TimelineGroup that already contains timelines. uid_map: Optional mapping from timeline.id to desired bundle UID. If not provided, each timeline’s id is used as its bundle UID.
Returns: self (for method chaining)
Raises: ValueError: If the group ID already exists in the bundle, or if any timeline UID would collide with an existing one.
Examples: Add a recording group with 5 DPTs:
>>> grp = TimelineGroup(id="normal", timelines=[dpt1, dpt2, dpt3])
>>> bundle.add_group(grp)
With custom UIDs:
>>> bundle.add_group(grp, uid_map={"dpt:1": "audio", "dpt:2": "midi"})
add_match_claims
AlignmentBundle.add_match_claims(claims)Add MatchClaims connecting timelines across different groups.
MatchClaims encode coordinate correspondences between timelines in different groups (e.g., EEP recording notes matched to ABC score notes). They enable cross-group coordinate transfer via MatchLine → WarpMap.
WarpMaps are built lazily on first transfer() or get_timestamp_at() call, so adding claims is cheap.
Args: claims: List of MatchClaim objects. Each synchronous claim connects two timelines via its start_anchor.
Returns: self (for method chaining)
add_timeline
AlignmentBundle.add_timeline(
timeline,
*,
uid=None,
aligned_to=None,
as_group=None,
start=None,
end=None,
)Add a timeline, optionally aligned to an existing timeline.
This is the primary method for adding timelines to the bundle. Timelines can be standalone or aligned to existing timelines.
Args: timeline: The Timeline to add. uid: Optional explicit ID. If None, uses timeline.id. aligned_to: ID of existing timeline to align with. If provided, both timelines become part of the same group. If the target timeline is not yet in a group, a new group is created with the target as reference. as_group: Name for the group if creating a new one. start: Where this timeline’s 0-origin starts in the group. - IdCoordinate: Coordinate with explicit timeline_id (preferred) - (coord, timeline_id): Legacy tuple form - float: Coordinate in the aligned_to timeline - None: Use group’s current start (default for linear alignment) end: Where this timeline’s end (length) aligns in the group. - Same options as start - None: Use group’s current end (default for linear alignment)
Returns: self (for method chaining)
Raises: ValueError: If uid already exists in bundle. KeyError: If aligned_to references a non-existent timeline.
Examples: Linear (full-extent) alignment:
>>> bundle.add_timeline(audio, uid="dgt1")
>>> bundle.add_timeline(midi, uid="dlt1", aligned_to="dgt1")
Partial alignment (SUPRA piano roll) using IdCoordinate:
>>> from timetoalign import IdCoordinate, TimeUnit
>>> bundle.add_timeline(image, uid="dgt1") # Full image
>>> bundle.add_timeline(
... holes,
... uid="dgt1_holes",
... aligned_to="dgt1",
... start=IdCoordinate(15343.0, TimeUnit.pixels, "dgt1"),
... end=IdCoordinate(293119.0, TimeUnit.pixels, "dgt1"),
... )
are_commensurable
AlignmentBundle.are_commensurable(timeline_a, timeline_b)Check if two timelines can be connected via transfer.
Two timelines are commensurable if they share the same group or if a cross-group path exists via MatchClaims.
Args: timeline_a: First timeline ID (bundle UID). timeline_b: Second timeline ID (bundle UID).
Returns: True if coordinates can be transferred between them.
create_match_claims
AlignmentBundle.create_match_claims(
event_pairs,
*,
synchronous=True,
agent='user',
decision_criteria='manual',
)Create MatchClaims from a list of event pairs.
Convenience factory for creating multiple MatchClaims from paired events. Each tuple specifies two events and their timeline IDs.
Args: event_pairs: List of tuples, each containing: (event_a, timeline_a_id, event_b, timeline_b_id). Events must have at least a start key with a coordinate. If both events also have an end key, the resulting MatchClaim will be an interval match (with both start_anchor and end_anchor). synchronous: Whether the matches are temporally synchronous. agent: Name of the agent creating the claims (for provenance). decision_criteria: How the match was determined (e.g., “manual”, “dynamic_time_warping”, “segment_correspondence”).
Returns: List of MatchClaim objects. Also automatically adds them to the bundle’s cross_group_claims.
Raises: ValueError: If event dicts are missing required keys.
Examples: >>> pairs = [ … ({“start”: 0.0}, “score”, {“start”: 45.5}, “audio”), … ({“start”: 10.0}, “score”, {“start”: 55.0}, “audio”), … ] >>> claims = bundle.create_match_claims(pairs, agent=“manual_alignment”) >>> len(claims) 2
diagram
AlignmentBundle.diagram(
width=80,
show_children=True,
max_children=6,
max_standalone=6,
unicode=True,
)Generate ASCII diagram for this bundle.
Args: width: Total width of the diagram in characters. show_children: Whether to expand child timelines. max_children: Maximum children per timeline. max_standalone: Maximum standalone timelines to display before truncating with an ellipsis. unicode: Use Unicode characters (True) or ASCII fallback (False).
Returns: Diagram object (displays as ASCII in terminal, rich HTML in Jupyter).
Examples: >>> print(bundle.diagram()) AlignmentBundle[thoresen_alignment]
TimelineGroup[dgt1_group] (2 timelines, 2 timestamps)
┌──────────────────────────────────────────────────────┐
│ DiscreteGraphicalTimeline[dgt1:1] (11 events) │
│ 0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 4835 pixels │
└──────────────────────────────────────────────────────┘
Timestamps: 2
MatchClaims: 5
get_group
AlignmentBundle.get_group(uid)Get a group by ID.
Supports partial string and regex matching: 1. Exact match: If uid matches an ID exactly, returns it. 2. Substring match: If uid is a substring of exactly one ID, returns that group. If multiple match, returns the first and warns. 3. Regex match: If uid is a valid regex, matches via re.search(). Same first-match logic with warning.
Args: uid: The group’s unique identifier, or a partial/regex pattern.
Returns: The TimelineGroup object.
Raises: KeyError: If no group matches the pattern.
Examples: >>> bundle.get_group(“score”) # Substring match >>> bundle.get_group(r”^perf”) # Regex match
get_group_for_timeline
AlignmentBundle.get_group_for_timeline(timeline_id)Get the group containing a timeline.
Args: timeline_id: The timeline’s unique identifier.
Returns: The TimelineGroup containing the timeline, or None if standalone.
get_match_claims
AlignmentBundle.get_match_claims(
timeline_id=None,
timeline_ids=None,
id_pattern=None,
between=None,
synchronous_only=False,
nomatch_only=False,
include_domains=None,
include_units=None,
)Query MatchClaims connecting timelines across groups.
This is the primary interface for accessing alignment information. All parameters are optional; when none are provided, returns all claims.
Filters are combined with AND logic: a claim must satisfy every non-None criterion. Uses the Unified Filter API.
Args: timeline_id: Return claims involving this timeline. timeline_ids: Return claims involving any of these timelines. id_pattern: Regex pattern matched against timeline IDs via re.search(). Example: r"^perf:" matches all performance timelines. between: Return claims connecting exactly these two timelines (order-independent). synchronous_only: Exclude non-synchronous (NOMATCH) claims. nomatch_only: Return only non-synchronous (NOMATCH) claims. include_domains: Only timelines in these domains. include_units: Only timelines with these units.
Returns: Filtered list of MatchClaims.
Examples: Get all synchronous claims for a performer::
>>> claims = bundle.get_match_claims(
... id_pattern=r"dlt1$", synchronous_only=True
... )
Get NOMATCH claims for a specific pair::
>>> nomatches = bundle.get_match_claims(
... between=("score:clt1", "perf:dlt5"),
... nomatch_only=True,
... )
get_matchstamp_at
AlignmentBundle.get_matchstamp_at(
coordinate,
timeline_id,
*,
conversion_maps=True,
timeline_ids=None,
id_pattern=None,
include_domains=None,
include_units=None,
)Get a cross-group MatchStamp at a coordinate on a timeline.
This is the primary interface for cross-domain coordinate transfer. Given a coordinate on one timeline, returns coordinates on ALL connected timelines across ALL groups.
The method: 1. Builds (or retrieves from cache) the MatchGraph for this coordinate’s connected component. 2. Returns the graph’s MatchStamp.
Args: coordinate: The coordinate value. timeline_id: Bundle UID of the source timeline. conversion_maps: Include C-map conversions. Reserved for future use (currently ignored). timeline_ids: Only include these timelines in the result. id_pattern: Regex filter for timeline IDs in the result. include_domains: Only these domains in the result. include_units: Only these units in the result.
Returns: MatchStamp spanning all connected timelines.
Raises: KeyError: If timeline_id is not in the bundle. ValueError: If no synchronous claims touch this coordinate.
Examples: >>> ms = bundle.get_matchstamp_at(10.0, “score:clt1”) >>> ms.n_timelines 23 # score + 22 performers
get_matchstamp_table
AlignmentBundle.get_matchstamp_table(claims=None, *, timeline_filter=None)Get a PyArrow table of MatchStamps for alignment queries.
Analogous to get_timestamp_table() but for cross-group alignment. Each row represents one MatchClaim/coordinate; columns are timeline IDs with their coordinate values.
Args: claims: List of MatchClaims to tabulate. If None, uses all cross_group_claims. timeline_filter: Only include these timeline columns.
Returns: PyArrow Table with one row per synchronous claim, one column per timeline. Non-synchronous claims are excluded.
Examples: >>> table = bundle.get_matchstamp_table() >>> table.num_rows 100 >>> table.column_names [‘score:clt1’, ‘perf:dlt1’, ‘perf:dlt2’, …]
get_matchstamps
AlignmentBundle.get_matchstamps(claims=None, *, from_graph=True)Get MatchStamps for a list of MatchClaims.
Convenience method for retrieving MatchStamps for multiple claims at once. Uses the bundle’s caching mechanism for efficient retrieval.
Args: claims: List of MatchClaims to get stamps for. If None, uses all cross_group_claims. from_graph: If True (default), return full MatchStamps from the MatchGraph (all connected timelines). If False, return reduced 2-timeline stamps.
Returns: List of MatchStamp objects. Non-synchronous claims yield None entries (filtered out).
Examples: >>> stamps = bundle.get_matchstamps() >>> len(stamps) 100 >>> stamps[0].n_timelines 23
get_timeline
AlignmentBundle.get_timeline(uid)Get a timeline by ID.
Supports partial string and regex matching: 1. Exact match: If uid matches an ID exactly, returns it. 2. Substring match: If uid is a substring of exactly one ID, returns that timeline. If multiple match, returns the first and warns. 3. Regex match: If uid is a valid regex, matches via re.search(). Same first-match logic with warning.
Args: uid: The timeline’s unique identifier, or a partial/regex pattern.
Returns: The Timeline object.
Raises: KeyError: If no timeline matches the pattern.
Examples: >>> bundle.get_timeline(“clt1”) # Exact match >>> bundle.get_timeline(“score”) # Substring match >>> bundle.get_timeline(r”^perf:“) # Regex match
get_timelines
AlignmentBundle.get_timelines(ids)Get multiple timelines by their IDs.
Convenience method for retrieving several timelines at once. Each ID supports partial string and regex matching (via get_timeline()).
Args: ids: List of timeline IDs (or partial/regex patterns).
Returns: List of Timeline objects in the same order as the input IDs.
Raises: KeyError: If any timeline ID is not found.
Examples: >>> timelines = bundle.get_timelines([“score”, “audio”, “midi”]) >>> len(timelines) 3
get_timestamp_at
AlignmentBundle.get_timestamp_at(coordinate, timeline_id, *, format='prefix')Get a cross-group timestamp at a coordinate on a given timeline.
This is the primary method for cross-domain coordinate transfer. Given a coordinate on one timeline, it returns the corresponding coordinates on ALL connected timelines across ALL groups.
The method:
- Finds the group containing the source timeline.
- Gets the within-group timestamp (via
group.get_timestamp_at()). - For each connected group (via
MatchLine/WarpMapfrom MatchClaims), transfers the coordinate and gets the within-group timestamp. - Returns all coordinates in the requested format.
Args: coordinate: The coordinate value on the source timeline. timeline_id: ID of the source timeline (bundle UID). format: Output format: - "prefix" (default): {"group/timeline": coord, ...} - "nested": {"group": {"timeline": coord, ...}, ...} - "flat": {"timeline": coord, ...}
Returns: Dict of coordinates across all connected groups and timelines. Timelines that cannot be reached return None values.
Raises: KeyError: If timeline_id is not in the bundle.
Examples: >>> ts = bundle.get_timestamp_at(50.0, “clt1_score”, format=“prefix”) >>> ts {‘score/clt1_score’: 50.0, ‘score/dgt1’: 45000.0, ‘normal/dpt1’: 23456789, …}
>>> ts = bundle.get_timestamp_at(50.0, "clt1_score", format="nested")
>>> ts
{'score': {'clt1_score': 50.0, 'dgt1': 45000.0},
'normal': {'dpt1': 23456789, ...}, ...}
summary
AlignmentBundle.summary()Get a summary of the bundle contents.
Returns a deterministic representation suitable for comparison. Keys and timeline lists are sorted for order-independence.
Returns: Dictionary with bundle information.
transfer
AlignmentBundle.transfer(coord, from_timeline, to_timeline)Transfer a coordinate from one timeline to another.
Automatically determines the conversion path:
- If both timelines are in the same group: direct conversion via
TimelineGroup.convert(). - If in different groups with MatchClaims: builds a
MatchLineandWarpMap(cached) and usesWarpMap.forward()to interpolate the coordinate. - If no path exists: returns
None.
Low-level coordinate transfer utility. For user-facing coordinate queries, use get_matchstamp_at() which returns a full cross-section as a MatchStamp.
Args: coord: The coordinate value to transfer. from_timeline: Bundle UID of the source timeline. to_timeline: Bundle UID of the target timeline.
Returns: The transferred coordinate, or None if no path exists.
Raises: KeyError: If either timeline is not in the bundle.
transfer_interval
AlignmentBundle.transfer_interval(start, end, from_timeline, to_timeline)Transfer an interval from one timeline to another.
Args: start: Start coordinate in source timeline. end: End coordinate in source timeline. from_timeline: ID of the source timeline. to_timeline: ID of the target timeline.
Returns: Tuple of (start, end) in target timeline, or None if no path.