Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Update multi-file visualization
Browse files
utils.py
CHANGED
|
@@ -461,10 +461,10 @@ def build_multifile_category_df(validNames, results, summaries, categories, cate
|
|
| 461 |
] if i < len(categorySelect) else []
|
| 462 |
valid_sps = [sp for sp in assigned_sps if sp in currAnnotation.labels()]
|
| 463 |
if valid_sps:
|
| 464 |
-
val = su.sumTimes(currAnnotation.subset(valid_sps)) / safe_total
|
| 465 |
else:
|
| 466 |
val = 0.0
|
| 467 |
-
df6_dict[category].append(min(val,
|
| 468 |
filled.add(category)
|
| 469 |
|
| 470 |
# For unassigned speakers: each gets their own column
|
|
@@ -477,8 +477,8 @@ def build_multifile_category_df(validNames, results, summaries, categories, cate
|
|
| 477 |
unassigned = [sp for sp in currAnnotation.labels() if sp not in assigned_all]
|
| 478 |
for sp in unassigned:
|
| 479 |
display = renames.get(sp, sp)
|
| 480 |
-
val = su.sumTimes(currAnnotation.subset([sp])) / safe_total
|
| 481 |
-
df6_dict[display].append(min(val,
|
| 482 |
filled.add(display)
|
| 483 |
|
| 484 |
# Fill 0 for every allCategories column not touched this row
|
|
@@ -491,16 +491,16 @@ def build_multifile_category_df(validNames, results, summaries, categories, cate
|
|
| 491 |
|
| 492 |
def build_multifile_role_voice_df(validNames, results, summaries, categories,
|
| 493 |
categorySelect, speakerRenames=None):
|
| 494 |
-
"""Build df8: per-file
|
| 495 |
-
Multi Voice and No Voice.
|
| 496 |
|
| 497 |
-
|
| 498 |
-
(speakers in single-voice segments that haven't been assigned to any role).
|
| 499 |
-
Multi Voice and No Voice come from df5 percentiles (0-100 scale) converted
|
| 500 |
-
to 0-1 proportions.
|
| 501 |
|
| 502 |
-
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
"""
|
| 505 |
speakerRenames = speakerRenames or {}
|
| 506 |
col_names = list(categories) + ["Unassigned", "Multi Voice", "No Voice"]
|
|
@@ -510,11 +510,45 @@ def build_multifile_role_voice_df(validNames, results, summaries, categories,
|
|
| 510 |
|
| 511 |
for fn in validNames:
|
| 512 |
currAnnotation, totalSeconds = results[fn]
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
|
| 517 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
assigned_all = set()
|
| 519 |
for i, category in enumerate(categories):
|
| 520 |
assigned_sps = [
|
|
@@ -525,37 +559,73 @@ def build_multifile_role_voice_df(validNames, results, summaries, categories,
|
|
| 525 |
valid_sps = [sp for sp in assigned_sps if sp in currAnnotation.labels()]
|
| 526 |
assigned_all.update(valid_sps)
|
| 527 |
if valid_sps:
|
| 528 |
-
val = su.sumTimes(currAnnotation.subset(valid_sps)) / safe_total
|
| 529 |
else:
|
| 530 |
val = 0.0
|
| 531 |
-
df8_dict[category].append(min(val,
|
| 532 |
|
| 533 |
# Unassigned speakers
|
| 534 |
unassigned_sps = [sp for sp in currAnnotation.labels() if sp not in assigned_all]
|
| 535 |
if unassigned_sps:
|
| 536 |
-
val = su.sumTimes(currAnnotation.subset(unassigned_sps)) / safe_total
|
| 537 |
else:
|
| 538 |
val = 0.0
|
| 539 |
-
df8_dict["Unassigned"].append(min(val,
|
| 540 |
-
|
| 541 |
-
# Multi Voice and No Voice from df5 percentiles (0-100 → 0-1)
|
| 542 |
-
partial = summaries[fn]["df5"]
|
| 543 |
-
df8_dict["No Voice"].append(partial["percentiles"][0] / 100)
|
| 544 |
-
df8_dict["Multi Voice"].append(partial["percentiles"][2] / 100)
|
| 545 |
|
| 546 |
return pd.DataFrame(df8_dict), col_names
|
| 547 |
|
| 548 |
|
| 549 |
-
def build_multifile_voice_df(validNames,
|
| 550 |
-
"""Build df7 (no/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
voiceNames = ["No Voice", "Single Voice", "Multi Voice"]
|
| 552 |
df7_dict = {"files": validNames}
|
| 553 |
for name in voiceNames:
|
| 554 |
df7_dict[name] = []
|
| 555 |
|
| 556 |
for fn in validNames:
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
|
| 561 |
return pd.DataFrame(df7_dict), voiceNames
|
|
|
|
| 461 |
] if i < len(categorySelect) else []
|
| 462 |
valid_sps = [sp for sp in assigned_sps if sp in currAnnotation.labels()]
|
| 463 |
if valid_sps:
|
| 464 |
+
val = su.sumTimes(currAnnotation.subset(valid_sps)) / safe_total * 100
|
| 465 |
else:
|
| 466 |
val = 0.0
|
| 467 |
+
df6_dict[category].append(min(val, 100.0))
|
| 468 |
filled.add(category)
|
| 469 |
|
| 470 |
# For unassigned speakers: each gets their own column
|
|
|
|
| 477 |
unassigned = [sp for sp in currAnnotation.labels() if sp not in assigned_all]
|
| 478 |
for sp in unassigned:
|
| 479 |
display = renames.get(sp, sp)
|
| 480 |
+
val = su.sumTimes(currAnnotation.subset([sp])) / safe_total * 100
|
| 481 |
+
df6_dict[display].append(min(val, 100.0))
|
| 482 |
filled.add(display)
|
| 483 |
|
| 484 |
# Fill 0 for every allCategories column not touched this row
|
|
|
|
| 491 |
|
| 492 |
def build_multifile_role_voice_df(validNames, results, summaries, categories,
|
| 493 |
categorySelect, speakerRenames=None):
|
| 494 |
+
"""Build df8: per-file percentages split by role plus Multi Voice and No Voice.
|
|
|
|
| 495 |
|
| 496 |
+
All values are on a 0-100 scale (% of file duration).
|
|
|
|
|
|
|
|
|
|
| 497 |
|
| 498 |
+
Multi Voice and No Voice are computed directly from the raw annotation using
|
| 499 |
+
pyannote timeline intersection — this avoids the index-step scale mismatch
|
| 500 |
+
from annotationToNoiseList/df5 percentiles.
|
| 501 |
+
|
| 502 |
+
Role columns use sumTimes on the union of role speaker segments (overlaps
|
| 503 |
+
within a role counted once).
|
| 504 |
"""
|
| 505 |
speakerRenames = speakerRenames or {}
|
| 506 |
col_names = list(categories) + ["Unassigned", "Multi Voice", "No Voice"]
|
|
|
|
| 510 |
|
| 511 |
for fn in validNames:
|
| 512 |
currAnnotation, totalSeconds = results[fn]
|
| 513 |
+
# Use annotation's actual span as denominator to avoid truncation issues
|
| 514 |
+
annotation_end = max(
|
| 515 |
+
(s.end for s in currAnnotation.itersegments()),
|
| 516 |
+
default=totalSeconds
|
| 517 |
+
)
|
| 518 |
+
safe_total = max(annotation_end, totalSeconds, 1)
|
| 519 |
+
prefix = fn + ": "
|
| 520 |
+
renames = speakerRenames.get(fn, {})
|
| 521 |
|
| 522 |
+
# Compute Multi Voice directly: time where 2+ speakers overlap
|
| 523 |
+
# by intersecting all pairs of speaker timelines
|
| 524 |
+
labels = [l for l in currAnnotation.labels()]
|
| 525 |
+
multi_voice_tl = currAnnotation.get_timeline(copy=False)
|
| 526 |
+
# Build per-speaker timelines and find overlapping regions
|
| 527 |
+
speaker_timelines = [
|
| 528 |
+
currAnnotation.subset([sp]).get_timeline(copy=False)
|
| 529 |
+
for sp in labels
|
| 530 |
+
]
|
| 531 |
+
# Multi Voice = union of all pairwise intersections
|
| 532 |
+
from pyannote.core import Timeline
|
| 533 |
+
multi_segments = []
|
| 534 |
+
for i in range(len(speaker_timelines)):
|
| 535 |
+
for j in range(i + 1, len(speaker_timelines)):
|
| 536 |
+
for seg_i in speaker_timelines[i]:
|
| 537 |
+
for seg_j in speaker_timelines[j]:
|
| 538 |
+
overlap = seg_i & seg_j
|
| 539 |
+
if overlap:
|
| 540 |
+
multi_segments.append(overlap)
|
| 541 |
+
multi_tl = Timeline(segments=multi_segments).support()
|
| 542 |
+
multi_voice_pct = multi_tl.duration() / safe_total * 100
|
| 543 |
+
|
| 544 |
+
# No Voice = total duration minus any-speaker coverage
|
| 545 |
+
any_voice_duration = currAnnotation.get_timeline(copy=False).support().duration()
|
| 546 |
+
no_voice_pct = max((safe_total - any_voice_duration) / safe_total * 100, 0.0)
|
| 547 |
+
|
| 548 |
+
df8_dict["Multi Voice"].append(min(multi_voice_pct, 100.0))
|
| 549 |
+
df8_dict["No Voice"].append(min(no_voice_pct, 100.0))
|
| 550 |
+
|
| 551 |
+
# Role proportions — sumTimes on union of role speakers (0-100 scale)
|
| 552 |
assigned_all = set()
|
| 553 |
for i, category in enumerate(categories):
|
| 554 |
assigned_sps = [
|
|
|
|
| 559 |
valid_sps = [sp for sp in assigned_sps if sp in currAnnotation.labels()]
|
| 560 |
assigned_all.update(valid_sps)
|
| 561 |
if valid_sps:
|
| 562 |
+
val = su.sumTimes(currAnnotation.subset(valid_sps)) / safe_total * 100
|
| 563 |
else:
|
| 564 |
val = 0.0
|
| 565 |
+
df8_dict[category].append(min(val, 100.0))
|
| 566 |
|
| 567 |
# Unassigned speakers
|
| 568 |
unassigned_sps = [sp for sp in currAnnotation.labels() if sp not in assigned_all]
|
| 569 |
if unassigned_sps:
|
| 570 |
+
val = su.sumTimes(currAnnotation.subset(unassigned_sps)) / safe_total * 100
|
| 571 |
else:
|
| 572 |
val = 0.0
|
| 573 |
+
df8_dict["Unassigned"].append(min(val, 100.0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 574 |
|
| 575 |
return pd.DataFrame(df8_dict), col_names
|
| 576 |
|
| 577 |
|
| 578 |
+
def build_multifile_voice_df(validNames, results):
|
| 579 |
+
"""Build df7 (no/single/multi voice percentages per file) for the multi-file expander.
|
| 580 |
+
|
| 581 |
+
All values are on a 0-100 scale (% of file duration).
|
| 582 |
+
|
| 583 |
+
Computed directly from the raw annotation using pyannote timeline methods
|
| 584 |
+
to avoid the index-step scale mismatch in annotationToNoiseList/df5:
|
| 585 |
+
- Multi Voice: union of all pairwise speaker timeline intersections
|
| 586 |
+
- Any Voice: support of the full annotation timeline
|
| 587 |
+
- No Voice: file duration minus Any Voice
|
| 588 |
+
- Single Voice: Any Voice minus Multi Voice
|
| 589 |
+
"""
|
| 590 |
+
from pyannote.core import Timeline
|
| 591 |
voiceNames = ["No Voice", "Single Voice", "Multi Voice"]
|
| 592 |
df7_dict = {"files": validNames}
|
| 593 |
for name in voiceNames:
|
| 594 |
df7_dict[name] = []
|
| 595 |
|
| 596 |
for fn in validNames:
|
| 597 |
+
currAnnotation, totalSeconds = results[fn]
|
| 598 |
+
annotation_end = max(
|
| 599 |
+
(s.end for s in currAnnotation.itersegments()),
|
| 600 |
+
default=totalSeconds
|
| 601 |
+
)
|
| 602 |
+
safe_total = max(annotation_end, totalSeconds, 1)
|
| 603 |
+
|
| 604 |
+
labels = list(currAnnotation.labels())
|
| 605 |
+
speaker_timelines = [
|
| 606 |
+
currAnnotation.subset([sp]).get_timeline(copy=False)
|
| 607 |
+
for sp in labels
|
| 608 |
+
]
|
| 609 |
+
|
| 610 |
+
# Multi Voice = union of all pairwise overlaps
|
| 611 |
+
multi_segments = []
|
| 612 |
+
for i in range(len(speaker_timelines)):
|
| 613 |
+
for j in range(i + 1, len(speaker_timelines)):
|
| 614 |
+
for seg_i in speaker_timelines[i]:
|
| 615 |
+
for seg_j in speaker_timelines[j]:
|
| 616 |
+
overlap = seg_i & seg_j
|
| 617 |
+
if overlap:
|
| 618 |
+
multi_segments.append(overlap)
|
| 619 |
+
multi_tl = Timeline(segments=multi_segments).support()
|
| 620 |
+
multi_pct = multi_tl.duration() / safe_total * 100
|
| 621 |
+
|
| 622 |
+
# Any Voice = support of full annotation
|
| 623 |
+
any_voice = currAnnotation.get_timeline(copy=False).support().duration()
|
| 624 |
+
no_voice_pct = max((safe_total - any_voice) / safe_total * 100, 0.0)
|
| 625 |
+
single_pct = max(any_voice / safe_total * 100 - multi_pct, 0.0)
|
| 626 |
+
|
| 627 |
+
df7_dict["No Voice"].append(min(no_voice_pct, 100.0))
|
| 628 |
+
df7_dict["Single Voice"].append(min(single_pct, 100.0))
|
| 629 |
+
df7_dict["Multi Voice"].append(min(multi_pct, 100.0))
|
| 630 |
|
| 631 |
return pd.DataFrame(df7_dict), voiceNames
|