Spaces:
Sleeping
Sleeping
Azure DevOps Pipeline
deploy: Merged PR 29: melhoria no projeto, refatoracao e desempenho para 100 pessoas logadas
07e5a95 | from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Callable, Iterable | |
| from music_token_metrics import ( | |
| eh_sem_acorde, | |
| normalizar_token, | |
| qualidade_completa_token, | |
| qualidade_token, | |
| raiz_token, | |
| ) | |
| class ChordInterval: | |
| start: float | |
| end: float | |
| label: str | |
| def __post_init__(self) -> None: | |
| if self.start < 0 or self.end <= self.start: | |
| raise ValueError(f"Intervalo invalido: {self.start}..{self.end}") | |
| def validate_timeline(intervals: Iterable[ChordInterval]) -> list[ChordInterval]: | |
| ordered = sorted(intervals, key=lambda item: (item.start, item.end)) | |
| previous_end = 0.0 | |
| for item in ordered: | |
| if item.start < previous_end - 1e-9: | |
| raise ValueError("Intervalos sobrepostos nao sao aceitos") | |
| previous_end = item.end | |
| return ordered | |
| def duration_weighted_accuracy( | |
| reference: Iterable[ChordInterval], | |
| estimated: Iterable[ChordInterval], | |
| comparator: Callable[[str, str], bool], | |
| ) -> float: | |
| reference_items = validate_timeline(reference) | |
| estimated_items = validate_timeline(estimated) | |
| boundaries = sorted({ | |
| point | |
| for item in [*reference_items, *estimated_items] | |
| for point in (item.start, item.end) | |
| }) | |
| correct = 0.0 | |
| evaluated = 0.0 | |
| for start, end in zip(boundaries, boundaries[1:]): | |
| if end <= start: | |
| continue | |
| midpoint = (start + end) / 2.0 | |
| expected = label_at(reference_items, midpoint) | |
| predicted = label_at(estimated_items, midpoint) | |
| duration = end - start | |
| evaluated += duration | |
| if comparator(expected, predicted): | |
| correct += duration | |
| return correct / evaluated if evaluated else 1.0 | |
| def boundary_f1( | |
| reference: Iterable[ChordInterval], | |
| estimated: Iterable[ChordInterval], | |
| tolerance_seconds: float = 0.5, | |
| ) -> dict[str, float | int]: | |
| if tolerance_seconds < 0: | |
| raise ValueError("A tolerancia deve ser nao negativa") | |
| expected = internal_boundaries(validate_timeline(reference)) | |
| predicted = internal_boundaries(validate_timeline(estimated)) | |
| unmatched = set(range(len(predicted))) | |
| matches = 0 | |
| for boundary in expected: | |
| candidates = [ | |
| index for index in unmatched | |
| if abs(predicted[index] - boundary) <= tolerance_seconds | |
| ] | |
| if not candidates: | |
| continue | |
| best = min(candidates, key=lambda index: abs(predicted[index] - boundary)) | |
| unmatched.remove(best) | |
| matches += 1 | |
| precision = matches / len(predicted) if predicted else (1.0 if not expected else 0.0) | |
| recall = matches / len(expected) if expected else (1.0 if not predicted else 0.0) | |
| f1 = (2 * precision * recall / (precision + recall)) if precision + recall else 0.0 | |
| return { | |
| "matches": matches, | |
| "reference_boundaries": len(expected), | |
| "estimated_boundaries": len(predicted), | |
| "precision": precision, | |
| "recall": recall, | |
| "f1": f1, | |
| } | |
| def evaluate_temporal_chords( | |
| reference: Iterable[ChordInterval], | |
| estimated: Iterable[ChordInterval], | |
| tolerance_seconds: float = 0.5, | |
| ) -> dict[str, object]: | |
| reference_items = validate_timeline(reference) | |
| estimated_items = validate_timeline(estimated) | |
| return { | |
| "exact_wcsr": duration_weighted_accuracy( | |
| reference_items, estimated_items, | |
| lambda left, right: normalizar_token(left) == normalizar_token(right), | |
| ), | |
| "root_wcsr": duration_weighted_accuracy( | |
| reference_items, estimated_items, | |
| lambda left, right: raiz_token(left) == raiz_token(right), | |
| ), | |
| "majmin_wcsr": duration_weighted_accuracy( | |
| reference_items, estimated_items, | |
| lambda left, right: ( | |
| raiz_token(left), qualidade_token(left) | |
| ) == ( | |
| raiz_token(right), qualidade_token(right) | |
| ), | |
| ), | |
| "full_quality_wcsr": duration_weighted_accuracy( | |
| reference_items, estimated_items, | |
| lambda left, right: ( | |
| raiz_token(left), qualidade_completa_token(left) | |
| ) == ( | |
| raiz_token(right), qualidade_completa_token(right) | |
| ), | |
| ), | |
| "no_chord_wcsr": duration_weighted_accuracy( | |
| reference_items, estimated_items, | |
| lambda left, right: eh_sem_acorde(left) == eh_sem_acorde(right), | |
| ), | |
| "boundaries": boundary_f1(reference_items, estimated_items, tolerance_seconds), | |
| } | |
| def label_at(intervals: list[ChordInterval], timestamp: float) -> str: | |
| for item in intervals: | |
| if item.start <= timestamp < item.end: | |
| return item.label | |
| return "N" | |
| def internal_boundaries(intervals: list[ChordInterval]) -> list[float]: | |
| return [item.end for item in intervals[:-1]] | |