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 MatchClaimMatchLineWarpMap 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 MatchLineWarpMap.

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:

  1. Finds the group containing the source timeline.
  2. Gets the within-group timestamp (via group.get_timestamp_at()).
  3. For each connected group (via MatchLine/WarpMap from MatchClaims), transfers the coordinate and gets the within-group timestamp.
  4. 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:

  1. If both timelines are in the same group: direct conversion via TimelineGroup.convert().
  2. If in different groups with MatchClaims: builds a MatchLine and WarpMap (cached) and uses WarpMap.forward() to interpolate the coordinate.
  3. 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.