How to Load an IEEE 1599 Document

IEEE 1599 multi-layer XML: spine/VTU hub relating score, page pixels, and audio as one multimodal AlignmentBundle

How to Load an IEEE 1599 Document

IEEE 1599 is a multi-layer XML standard for encoding a single musical work once and relating every representation of it — the score, engraved page images, audio recordings, analytical annotations — back to a common axis. That axis is the <spine>: a flat, ordered list of abstract events in virtual time units (VTU), unit-less by design. Every other layer states where in its own coordinate space one of those spine events falls, via an event_ref. The spine is the hub; the layers are projections onto it; event_ref is the correspondence — exactly the shape of an alignment.

Ieee1599Loader reads one such document into a single multimodal : the spine as one in ticks, the logically-organised-symbols (LOS) layer of notes, rests and lyrics on another, one graphical timeline per engraved edition, one physical timeline per audio track, and the whole set of projections as one columnar . The document’s own analytical layer — a segmentation of the spine resolving to places of a Petri net — is carried too, not as timing but as an external reference.

We load an existing document; nothing here runs an aligner. The correspondences are the document’s own event_ref cross-references; the loader’s job is to read them faithfully.

The work is Erik Satie’s Gymnopédie No. 1.

The arc:

  1. Load the document in one call and read its title.
  2. The spine, in ticks — a cumulative virtual-time axis — and the LOS layer of notes and rests sitting at spine coordinates.
  3. The graphical editions as accolade SegmentLines, in pixels, and the audio tracks, in seconds.
  4. The projections, reached through the uniform API, and the cross-section they describe over spine coordinates.
  5. The structural layer: a Petri-net analysis carried as external references on the spine.

Setup

from __future__ import annotations

import pyarrow.compute as pc

from timetoalign import IntervalToConstantMap
from timetoalign.alignment.claims import MatchClaim
from timetoalign.loader.alignment.ieee1599 import Ieee1599Loader
from timetoalign.testdata import ensure_data

base = ensure_data("ieee1599")
document_path = base / "SatiePetriNets" / "ieee1599" / "gymnopedie_01.xml"
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/__init__.py:9: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  import pkg_resources

1. Load the document in one call

Ieee1599Loader parses the whole document — spine, LOS, every graphical edition, every audio track, and the structural analysis — into curated tables, then assembles them into timelines and a claim field. from_file() is the one-line form of the standard two-phase loader pattern: parse, then build.

loader = Ieee1599Loader.from_file(document_path)
loader

Ieee1599Loader

Sources 1 file(s): gymnopedie_01.xml
Claims 2085
Unit ticks
File gymnopedie_01
Title Gymnopédie No. 1
Spine events 382
LOS events 557
Editions 2
Tracks 2
Claims 2085
Try create_timeline(), create_bundle(), get_events(...)

The document states its own bibliographic metadata, read straight off <general><description>:

{
    "title": loader.file_metadata.get("title"),
    "work_title": loader.file_metadata.get("work_title"),
    "authors": loader.file_metadata.get("authors"),
}
{'title': 'Gymnopédie No. 1',
 'work_title': '3 Gymnopédies',
 'authors': [{'name': 'Erik Satie', 'type': 'composer'}]}

create_bundle() builds the : six standalone timelines — the spine, the LOS layer, two engraved editions and two audio tracks — tied together by the MatchClaims. Each timeline lives in its own ; MatchClaims carry the connections between those groups.

bundle = loader.create_bundle()
print(bundle.diagram())
AlignmentBundle[bundle:AlignmentBundle_1]

  TimelineGroup[spine:dlt1] (1 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ DiscreteLogicalTimeline[spine:dlt1] (382 events)                           │
  │                        0 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 236544 ticks │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[los:dlt2] (1 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ DiscreteLogicalTimeline[los:dlt2] (557 events)                             │
  │                        0 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 236544 ticks │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[eng_montreal_les_editions_outremontaises_2006:dgt1] (1 timelines, 2 timestamps)
  ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ SegmentLine[DiscreteGraphicalTimeline][eng_montreal_les_editions_outremontaises_2006:dgt1] (382 events, 18 children, 1 cmaps) │
  │                       0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 7686 pixels                                                    │
  │   ├─ accolade_1       0 ∶∶                                     426 (22 events)                                                │
  │   ├─ accolade_2     426   ∶∶                                   846 (22 events)                                                │
  │   ├─ accolade_3     846     ∶∶                                 1262 (16 events)                                               │
  │   │  ... (12 more children)                                                                                                   │
  │   ├─ accolade_16   6372                                ∶∶      6813 (23 events)                                               │
  │   ├─ accolade_17   6813                                  ∶∶    7260 (23 events)                                               │
  │   └─ accolade_18   7260                                    ∶∶∶ 7686 (24 events)                                               │
  └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[eng_transcription_2012:dgt2] (1 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ SegmentLine[DiscreteGraphicalTimeline][eng_transcription_2012:dgt2] (382 events, 18 children, 1 cmaps) │
  │                       0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 7356 pixels                             │
  │   ├─ accolade_1       0 ∶∶                                     408 (22 events)                         │
  │   ├─ accolade_2     408   ∶∶                                   812 (22 events)                         │
  │   ├─ accolade_3     812     ∶∶                                 1211 (16 events)                        │
  │   │  ... (12 more children)                                                                            │
  │   ├─ accolade_16   6105                                ∶∶      6528 (23 events)                        │
  │   ├─ accolade_17   6528                                  ∶∶    6954 (23 events)                        │
  │   └─ accolade_18   6954                                    ∶∶∶ 7356 (24 events)                        │
  └────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[satie_gymnopedie1_coleman:cpt1] (1 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ ContinuousPhysicalTimeline[satie_gymnopedie1_coleman:cpt1] (382 events)    │
  │                       0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 188.8 seconds │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[satie_gymnopedie1_pfaul:cpt2] (1 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ ContinuousPhysicalTimeline[satie_gymnopedie1_pfaul:cpt2] (382 events)      │
  │                       0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 192.2 seconds │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  MatchClaims: 2085

2. The spine and the LOS layer

The spine: a cumulative virtual-time axis

The spine’s timing attribute is a relative integer delta against the previous <event>; the loader accumulates it into an absolute coordinate, so the stored value is the running sum — the cumulative VTU. Events notated as simultaneous carry timing="0" and land on the same coordinate. The spine is a DiscreteLogicalTimeline in ticks:

spine = bundle.get_timeline(loader.spine_uid)
spine
DiscreteLogicalTimeline[spine:dlt1] (382 events)
                       0 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 236544 ticks
spine.get_events().head(5)
id name temporal_type event_type start end duration hpos
0 Clef_part_1_1 None instant SpineEvent 0 None None 0
1 Clef_part_2_1 None instant SpineEvent 0 None None 0
2 KeySignature_part_1_1 None instant SpineEvent 0 None None 0
3 KeySignature_part_2_1 None instant SpineEvent 0 None None 0
4 TimeSignature_part_1_1 None instant SpineEvent 0 None None 0

The LOS layer: notes and rests at spine coordinates

Every LOS event — a Note, a Rest, or a lyric Syllable — sits at the VTU coordinate of the spine event its event_ref names; that reference is its temporal position; the LOS layer carries no timing of its own. It is a second DiscreteLogicalTimeline, sharing the spine’s unit and length but its own event set — 557 LOS events against 382 spine events, since a chord’s several noteheads and a measure’s clefs and key signatures do not correspond one-to-one.

los = bundle.get_timeline(loader.los_uid)
los
DiscreteLogicalTimeline[los:dlt2] (557 events)
                       0 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 236544 ticks

Notated durations are kept as the verbatim num/den pair rather than a reduced fraction, so a duration notated 1/4 never silently becomes something else; the exact value is Fraction(duration_num, duration_den). The opening melodic notes — each a quarter note (duration_num=1, duration_den=4) — carry their pitch as a step, octave and accidental, e.g. the first is F♯6:

notes = los.get_events(event_type="Note").to_dataframe()
notes.head(5)
id name temporal_type event_type start end duration actual_accidental augmentation_dots duration_den ... event_ref measure notehead_index octave part printed_accidental staff step tie voice
0 note:000001 None instant Note 13312 None None sharp NaN 4 ... part_1_voice0_measure5_ev1 5 0 6 part_1 None part_1_staff F False part_1_0_voice
1 note:000002 None instant Note 14336 None None natural NaN 4 ... part_1_voice0_measure5_ev2 5 0 6 part_1 None part_1_staff A False part_1_0_voice
2 note:000003 None instant Note 15360 None None natural NaN 4 ... part_1_voice0_measure6_ev0 6 0 6 part_1 None part_1_staff G False part_1_0_voice
3 note:000004 None instant Note 16384 None None sharp NaN 4 ... part_1_voice0_measure6_ev1 6 0 6 part_1 None part_1_staff F False part_1_0_voice
4 note:000005 None instant Note 17408 None None sharp NaN 4 ... part_1_voice0_measure6_ev2 6 0 6 part_1 None part_1_staff C False part_1_0_voice

5 rows × 21 columns


3. Editions in pixels, tracks in seconds

Per-edition graphical SegmentLines

Each <graphic_instance_group> — one engraved edition of the score — is its own SegmentLine[DiscreteGraphicalTimeline], in unit pixels. Its x coordinates zig-zag along the spine: when a new system begins beyond half of the preceding page’s x-span, the reset starts a new accolade. Each accolade is one contiguous segment on the edition’s SegmentLine; the two editions each have 18 segments, distributed 4 + 5 + 5 + 4 across their four pages.

for uid in loader.edition_uids:
    edition = bundle.get_timeline(uid)
    print(f"{edition.id}: {edition.name!r}, {edition.length}")
eng_montreal_les_editions_outremontaises_2006:dgt1: 'eng:Montréal: Les Éditions Outremontaises (2006)', 7686 pixels
eng_transcription_2012:dgt2: 'eng:Transcription (2012)', 7356 pixels
edition = bundle.get_timeline(loader.edition_uids[0])
graphical_events = edition.get_events().to_dataframe()
graphical_events.head()
id name temporal_type event_type start end duration bbox event_ref file_name position_in_group source_timeline
0 graphicevent:000001 None interval GraphicEvent 50 64 14 {'lr': {'x': 64, 'y': 164}, 'ul': {'x': 50, 'y... Clef_part_1_1 score/Montreal/IMSLP01599-Satie_Gymnopedies-1.png 1 eng_montreal_les_editions_outremontaises_2006:...
1 graphicevent:000002 None interval GraphicEvent 49 64 15 {'lr': {'x': 64, 'y': 208}, 'ul': {'x': 49, 'y... Clef_part_2_1 score/Montreal/IMSLP01599-Satie_Gymnopedies-1.png 1 eng_montreal_les_editions_outremontaises_2006:...
2 graphicevent:000003 None interval GraphicEvent 66 79 13 {'lr': {'x': 79, 'y': 153}, 'ul': {'x': 66, 'y... KeySignature_part_1_1 score/Montreal/IMSLP01599-Satie_Gymnopedies-1.png 1 eng_montreal_les_editions_outremontaises_2006:...
3 graphicevent:000004 None interval GraphicEvent 66 79 13 {'lr': {'x': 79, 'y': 211}, 'ul': {'x': 66, 'y... KeySignature_part_2_1 score/Montreal/IMSLP01599-Satie_Gymnopedies-1.png 1 eng_montreal_les_editions_outremontaises_2006:...
4 graphicevent:000005 None interval GraphicEvent 84 94 10 {'lr': {'x': 94, 'y': 158}, 'ul': {'x': 84, 'y... TimeSignature_part_1_1 score/Montreal/IMSLP01599-Satie_Gymnopedies-1.png 1 eng_montreal_les_editions_outremontaises_2006:...

The edition’s segments are the individual accolades. list_segments() preserves their spine order, while get_segment_by_index() exposes each segment’s global offset and its page-local graphical timeline:

print(f"{edition.class_name}: {edition.n_segments} accolade segments")
for index, segment_id in enumerate(edition.list_segments()):
    offset, accolade = edition.get_segment_by_index(index)
    print(f"{index + 1:>2}: {segment_id} at x={offset.value}, length={accolade.length}")
SegmentLine[DiscreteGraphicalTimeline]: 18 accolade segments
 1: eng_montreal_les_editions_outremontaises_2006:dgt1_segment1 at x=0, length=426 pixels
 2: eng_montreal_les_editions_outremontaises_2006:dgt1_segment2 at x=426, length=420 pixels
 3: eng_montreal_les_editions_outremontaises_2006:dgt1_segment3 at x=846, length=416 pixels
 4: eng_montreal_les_editions_outremontaises_2006:dgt1_segment4 at x=1262, length=432 pixels
 5: eng_montreal_les_editions_outremontaises_2006:dgt1_segment5 at x=1694, length=417 pixels
 6: eng_montreal_les_editions_outremontaises_2006:dgt1_segment6 at x=2111, length=422 pixels
 7: eng_montreal_les_editions_outremontaises_2006:dgt1_segment7 at x=2533, length=443 pixels
 8: eng_montreal_les_editions_outremontaises_2006:dgt1_segment8 at x=2976, length=447 pixels
 9: eng_montreal_les_editions_outremontaises_2006:dgt1_segment9 at x=3423, length=423 pixels
10: eng_montreal_les_editions_outremontaises_2006:dgt1_segment10 at x=3846, length=420 pixels
11: eng_montreal_les_editions_outremontaises_2006:dgt1_segment11 at x=4266, length=421 pixels
12: eng_montreal_les_editions_outremontaises_2006:dgt1_segment12 at x=4687, length=415 pixels
13: eng_montreal_les_editions_outremontaises_2006:dgt1_segment13 at x=5102, length=421 pixels
14: eng_montreal_les_editions_outremontaises_2006:dgt1_segment14 at x=5523, length=428 pixels
15: eng_montreal_les_editions_outremontaises_2006:dgt1_segment15 at x=5951, length=421 pixels
16: eng_montreal_les_editions_outremontaises_2006:dgt1_segment16 at x=6372, length=441 pixels
17: eng_montreal_les_editions_outremontaises_2006:dgt1_segment17 at x=6813, length=447 pixels
18: eng_montreal_les_editions_outremontaises_2006:dgt1_segment18 at x=7260, length=426 pixels

An IntervalToConstantMap on the edition resolves any of these unfolded x coordinates to the page-image file_name that contains it. The first graphical event provides one such coordinate:

page_image_map = next(
    cmap
    for cmap in edition._conversion_maps.values()
    if isinstance(cmap, IntervalToConstantMap)
)
first_graphical_event = graphical_events.iloc[0]
page_image_map(first_graphical_event["start"])
'score/Montreal/IMSLP01599-Satie_Gymnopedies-1.png'

Graphical geometry is kept as one nested bbox struct rather than split coordinate columns. Its ul and lr members preserve the upper-left and lower-right pixel coordinates of the graphical event:

bbox = first_graphical_event["bbox"]
{
    "bbox": bbox,
    "ul": bbox["ul"],
    "lr": bbox["lr"],
}
{'bbox': {'lr': {'x': 64, 'y': 164}, 'ul': {'x': 50, 'y': 132}},
 'ul': {'x': 50, 'y': 132},
 'lr': {'x': 64, 'y': 164}}

Per-track audio timelines

Each <track> — one recording — is its own ContinuousPhysicalTimeline in seconds. The performers and the media file name the document states ride along in meta; the media file itself is never opened:

for uid in loader.track_uids:
    track = bundle.get_timeline(uid)
    print(f"{track.id}: {track.meta['performers']}, {track.length}")
satie_gymnopedie1_coleman:cpt1: [{'name': 'Chase Coleman', 'type': 'piano'}], 188.77 seconds
satie_gymnopedie1_pfaul:cpt2: [{'name': 'Andreas Pfaul', 'type': 'piano'}], 192.22 seconds
track = bundle.get_timeline(loader.track_uids[0])
track.get_events().head(3)
id name temporal_type event_type start end duration event_ref file_name
0 trackevent:000001 None instant TrackEvent 2.46 None None Clef_part_1_1 audio/ChaseColeman/satie-gymnopedie1-coleman.mp3
1 trackevent:000002 None instant TrackEvent 2.46 None None Clef_part_2_1 audio/ChaseColeman/satie-gymnopedie1-coleman.mp3
2 trackevent:000003 None instant TrackEvent 2.46 None None KeySignature_part_1_1 audio/ChaseColeman/satie-gymnopedie1-coleman.mp3

4. The projections, as one columnar claim field

Every LOS, graphical and audio event contributes one synchronous tying its own coordinate to the spine event it references. All three layers go into one field — the alignment they express is one alignment, hub-and-spoke around the spine — reached through the uniform field API:

field = loader.get_field(MatchClaim)

{
    "field type": type(field).__name__,
    "claims": len(field),
}
{'field type': 'MatchClaimField', 'claims': 2085}

The MatchClaimField remains columnar; its table has one struct column. Showing its head makes that stored representation visible before any individual MatchClaim is materialised:

field.table.to_pandas().head()
match_claim
0 {'timeline_a_id': 'los:dlt2', 'timeline_b_id':...
1 {'timeline_a_id': 'los:dlt2', 'timeline_b_id':...
2 {'timeline_a_id': 'los:dlt2', 'timeline_b_id':...
3 {'timeline_a_id': 'los:dlt2', 'timeline_b_id':...
4 {'timeline_a_id': 'los:dlt2', 'timeline_b_id':...

Indexing the field materialises one MatchClaim on demand. This mid-document row is more illustrative than the first row because it shows an ordinary in-document correspondence rather than the special opening coordinate:

field[len(field) // 2]
MatchClaim synchronous, instant
Timeline A eng_transcription_2012:dgt2 @2102
Timeline B spine:dlt1 @64512
Metadata agent=Finale Plugin
Try: claim.get_matchstamp()

The cross-section over spine coordinates

get_matchstamp_table(from_graph=True) collapses the claims into one row per connected component of the alignment graph — one row per spine coordinate that at least one layer reaches, every participating layer filled in the same row. It is the cross-section of all six timelines over the spine:

cross_section = bundle.get_matchstamp_table(from_graph=True).to_pandas()

{"rows": len(cross_section), "columns": list(cross_section.columns)}
{'rows': 188,
 'columns': ['eng_montreal_les_editions_outremontaises_2006:dgt1',
  'eng_transcription_2012:dgt2',
  'los:dlt2',
  'satie_gymnopedie1_coleman:cpt1',
  'satie_gymnopedie1_pfaul:cpt2',
  'spine:dlt1']}
cross_section.head(5)
eng_montreal_les_editions_outremontaises_2006:dgt1 eng_transcription_2012:dgt2 los:dlt2 satie_gymnopedie1_coleman:cpt1 satie_gymnopedie1_pfaul:cpt2 spine:dlt1
0 49.0 75.0 0.0 2.46 0.50 0.0
1 133.0 148.0 1024.0 3.48 1.30 1024.0
2 192.0 201.0 3072.0 5.47 3.14 3072.0
3 224.0 231.0 4096.0 6.41 4.00 4096.0
4 286.0 284.0 6144.0 8.24 5.80 6144.0

5. The structural layer: an analysis resolved to a Petri net

An <analysis> partitions the spine into <segment> elements, each listing the spine events it covers; a sibling <petri_nets> block names .pnml files and binds one place of one net to one segment. That is a reference into an external resource, not a timing statement, so it is carried as spine.external_references — one row per (segment_event, place) pair — rather than as events or claims:

refs = spine.external_references
refs.num_rows
1795

Filtering to one segment shows the resolution end to end: every spine event of segment Analisi_1_L1_A resolves to place p2 of the Petri net stored in Analisi_1/L1.pnml — read straight off the document, with the .pnml file itself never opened:

segment_a = refs.filter(pc.equal(refs["comment"], "Analisi_1_L1_A"))
segment_a.to_pandas()
event_id external_id access_points comment
0 part_1_voice0_measure1_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
1 part_2_voice0_measure1_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
2 part_2_voice1_measure1_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
3 part_2_voice1_measure1_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
4 part_1_voice0_measure2_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
5 part_2_voice0_measure2_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
6 part_2_voice1_measure2_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
7 part_2_voice1_measure2_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
8 part_1_voice0_measure3_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
9 part_2_voice0_measure3_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
10 part_2_voice1_measure3_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
11 part_2_voice1_measure3_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
12 part_1_voice0_measure4_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
13 part_2_voice0_measure4_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
14 part_2_voice1_measure4_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
15 part_2_voice1_measure4_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
16 part_1_voice0_measure40_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
17 part_2_voice0_measure40_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
18 part_2_voice1_measure40_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
19 part_2_voice1_measure40_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
20 part_1_voice0_measure41_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
21 part_2_voice0_measure41_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
22 part_2_voice1_measure41_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
23 part_2_voice1_measure41_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
24 part_1_voice0_measure42_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
25 part_2_voice0_measure42_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
26 part_2_voice1_measure42_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
27 part_2_voice1_measure42_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
28 part_1_voice0_measure43_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
29 part_2_voice0_measure43_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
30 part_2_voice1_measure43_ev0 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A
31 part_2_voice1_measure43_ev1 p2 [{'uri': 'Analisi_1/L1.pnml', 'kind': 'relativ... Analisi_1_L1_A

A segment no <place> names keeps its row rather than being dropped: external_id falls back to the segment id, access_points is empty, and the comment records why. Exactly one segment of this analysis is unmapped:

unmapped = refs.filter(pc.equal(pc.list_value_length(refs["access_points"]), 0))
unmapped.to_pandas()
event_id external_id access_points comment
0 part_1_voice0_measure72_ev0 Analisi_1_L3_RS_I_23 [] segment without petri-net node

Recap

What the bundle expresses How
The spine, a cumulative VTU axis spine:dlt1, DiscreteLogicalTimeline, ticks
Notes, rests, lyrics at spine coordinates los:dlt2, verbatim duration_num/duration_den
Engraved editions, page-image boxes one SegmentLine[DiscreteGraphicalTimeline]
per edition: 18 accolade segments, an IntervalToConstantMap to page images, pixels
Audio recordings one ContinuousPhysicalTimeline per track, seconds
Every projection onto the spine one columnar via loader.get_field(MatchClaim)
The cross-section over spine coordinates bundle.get_matchstamp_table(from_graph=True)
The Petri-net analysis spine.external_references — segment → place, resolved without opening any .pnml

One IEEE 1599 document — one spine, several projections, one analytical annotation layer — loaded into a single in which every representation of the work stays reachable from the axis the document itself defines.