Skip to content

Commit

Permalink
Merge pull request #117 from napulen/rntxt_bug
Browse files Browse the repository at this point in the history
Evaluation bugs
  • Loading branch information
napulen authored Aug 7, 2022
2 parents c1a412a + b793ce5 commit 6d1fb57
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 141 deletions.
2 changes: 1 addition & 1 deletion AugmentedNet/annotation_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@


def _m21Parse(f):
return music21.converter.parse(f, format="romantext")
return music21.converter.parse(f, format="romantext", forceSource=True)


def from_tsv(tsv, sep="\t"):
Expand Down
62 changes: 40 additions & 22 deletions AugmentedNet/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from . import __version__
from . import cli
from .chord_vocabulary import frompcset
from .chord_vocabulary import frompcset, cosineSimilarity
from .cache import forceTonicization, getTonicizationScaleDegree
from .score_parser import parseScore
from .input_representations import available_representations as availableInputs
Expand Down Expand Up @@ -56,15 +56,27 @@ def solveChordSegmentation(df):
return df.dropna()[df.HarmonicRhythm7 == 0]


def resolveRomanNumeral(b, t, a, s, pcs, key, tonicizedKey):
def resolveRomanNumeralCosine(b, t, a, s, pcs, key, numerator, tonicizedKey):
pcsetVector = np.zeros(12)
chord = music21.chord.Chord(f"{b}2 {t}3 {a}4 {s}5")
pcset = tuple(sorted(set(chord.pitchClasses)))
# if the SATB notes don't make sense, use the pcset classifier
if pcset not in frompcset:
# which is guaranteed to exist in the chord vocabulary
pcset = pcs
# if the chord is nondiatonic to the tonicizedKey
# force a tonicization where the chord does exist
for n in chord.pitches:
pcsetVector[n.pitchClass] += 1
for pc in pcs:
pcsetVector[pc] += 1
chordNumerator = music21.roman.RomanNumeral(
numerator.replace("Cad", "Cad64"), tonicizedKey
).pitchClasses
for pc in chordNumerator:
pcsetVector[pc] += 1
smallestDistance = -2
for pcs in frompcset:
v2 = np.zeros(12)
for p in pcs:
v2[p] = 1
similarity = cosineSimilarity(pcsetVector, v2)
if similarity > smallestDistance:
pcset = pcs
smallestDistance = similarity
if tonicizedKey not in frompcset[pcset]:
# print("Forcing a tonicization")
candidateKeys = list(frompcset[pcset].keys())
Expand All @@ -83,6 +95,8 @@ def resolveRomanNumeral(b, t, a, s, pcs, key, tonicizedKey):
elif invfigure in ["6", "64"]:
rnfigure += invfigure
rn = rnfigure
if numerator == "Cad" and inv == 2:
rn = "Cad64"
if tonicizedKey != key:
denominator = getTonicizationScaleDegree(key, tonicizedKey)
rn = f"{rn}/{denominator}"
Expand All @@ -92,22 +106,17 @@ def resolveRomanNumeral(b, t, a, s, pcs, key, tonicizedKey):
return rn, chordLabel


def generateRomanText(h):
def generateRomanText(h, ts):
metadata = h.metadata
metadata.composer = metadata.composer or "Unknown"
metadata.title = metadata.title or "Unknown"
composer = metadata.composer.split("\n")[0]
title = metadata.title.split("\n")[0]
ts = {
(ts.measureNumber, float(ts.beat)): ts.ratioString
for ts in h.flat.getElementsByClass("TimeSignature")
}
rntxt = f"""\
Composer: {composer}
Title: {title}
Analyst: AugmentedNet v{__version__} - https://github.com/napulen/AugmentedNet
"""
setKey = False
currentMeasure = -1
for n in h.flat.notes:
if not n.lyric:
Expand Down Expand Up @@ -160,14 +169,20 @@ def predict(model, inputPath):
dfout["offset"] = paddedIndex
dfout["measure"] = paddedMeasure
chords = solveChordSegmentation(dfout)
s = music21.converter.parse(inputPath)
s = music21.converter.parse(inputPath, forceSource=True)
ts = {
(ts.measureNumber, float(ts.beat)): ts.ratioString
for ts in s.flat.getElementsByClass("TimeSignature")
}
schord = s.chordify().flat.notesAndRests
schord.metadata = s.metadata
# remove all lyrics from score
for note in s.recurse().notes:
note.lyrics = []
# for note in s.recurse().notes:
# note.lyrics = []
prevkey = ""
for analysis in chords.itertuples():
notes = []
for n in s.flat.notes.getElementsByOffset(analysis.offset):
for n in schord.getElementsByOffset(analysis.offset):
if isinstance(n, music21.note.Note):
notes.append((n, n.pitch.midi))
elif isinstance(n, music21.chord.Chord) and not isinstance(
Expand All @@ -180,13 +195,15 @@ def predict(model, inputPath):
thiskey = analysis.LocalKey38
tonicizedKey = analysis.TonicizedKey38
pcset = analysis.PitchClassSet121
rn2, chordLabel = resolveRomanNumeral(
numerator = analysis.RomanNumeral31
rn2, chordLabel = resolveRomanNumeralCosine(
analysis.Bass35,
analysis.Tenor35,
analysis.Alto35,
analysis.Soprano35,
pcset,
thiskey,
numerator,
tonicizedKey,
)
if thiskey != prevkey:
Expand All @@ -196,12 +213,12 @@ def predict(model, inputPath):
rn2fig = rn2
bass.addLyric(formatRomanNumeral(rn2fig, thiskey))
bass.addLyric(formatChordLabel(chordLabel))
rntxt = generateRomanText(s)
rntxt = generateRomanText(schord, ts)
filename, _ = inputPath.rsplit(".", 1)
annotatedScore = f"{filename}_annotated.musicxml"
annotationCSV = f"{filename}_annotated.csv"
annotatedRomanText = f"{filename}_annotated.rntxt"
s.write(fp=annotatedScore)
schord.write(fp=annotatedScore)
dfout.to_csv(annotationCSV)
with open(annotatedRomanText, "w") as fd:
fd.write(rntxt)
Expand All @@ -224,6 +241,7 @@ def batch(inputPath, dir, modelPath, useGpu):
# do not recursively annotate an annotated_file
continue
filepath = os.path.join(root, f)
print(filepath)
predict(model, inputPath=filepath)


Expand Down
2 changes: 1 addition & 1 deletion AugmentedNet/score_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


def _m21Parse(f, fmt=None):
s = music21.converter.parse(f, format=fmt)
s = music21.converter.parse(f, format=fmt, forceSource=True)
perc = [
p
for p in s.parts
Expand Down
29 changes: 9 additions & 20 deletions misc/compare_rntxt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from requests import PreparedRequest

from AugmentedNet.annotation_parser import parseAnnotation
from AugmentedNet.feature_representation import COMMON_ROMAN_NUMERALS


def compare_annotation_df(a, b):
b = b.reindex_like(a, method="ffill")
b = b.fillna(method="bfill")
pcset_accuracy = sum(a.a_pcset == b.a_pcset) / len(a.index)
key_accuracy = sum(a.a_tonicizedKey == b.a_tonicizedKey) / len(a.index)
inversion_accuracy = sum(a.a_inversion == b.a_inversion) / len(a.index)
Expand All @@ -19,8 +16,6 @@ def compare_annotation_df(a, b):


def compute_confusion_matrix(a, b):
b = b.reindex_like(a, method="ffill")
b = b.fillna(method="bfill")
roman_numeral_classes = len(COMMON_ROMAN_NUMERALS)
matrix = np.zeros((roman_numeral_classes, roman_numeral_classes))
for gt, pred in zip(a.a_romanNumeral, b.a_romanNumeral):
Expand All @@ -35,18 +30,6 @@ def compute_confusion_matrix(a, b):
groundtruth = "predictions_groundtruth"
models = [m for m in os.listdir(root) if m != groundtruth and "rntxt" in m]
gtdir = os.path.join(root, groundtruth)
crashing_july_7_2022 = [
"abc-op127-2.rntxt",
"abc-op18-no1-1.rntxt",
"bps-07-op010-no3-1.rntxt",
"bps-10-op014-no2-1.rntxt",
"bps-23-op057-appassionata-1.rntxt",
"tavern-beethoven-woo-75-a.rntxt",
"tavern-beethoven-woo-75-b.rntxt",
"wir-openscore-liedercorpus-hensel-6-lieder-op-9-1-die-ersehnte.rntxt",
"wir-openscore-liedercorpus-schumann-dichterliebe-op-48-16-die-alten-bosen-lieder.rntxt",
"wirwtc-bach-wtc-i-8.rntxt",
]
dfdict = {
"file": [],
"model": [],
Expand All @@ -58,8 +41,8 @@ def compute_confusion_matrix(a, b):
}
i = 0
for f in sorted(os.listdir(gtdir)):
if f in crashing_july_7_2022 or "wir-bach-chorales-19" not in f:
continue
# if "bps-07" not in f:
# continue
print(f)
path = os.path.join(root, groundtruth, f)
a = parseAnnotation(path)
Expand All @@ -68,7 +51,13 @@ def compute_confusion_matrix(a, b):
if not os.path.exists(mpath):
continue
print(f"\t{mpath}", end=" ")
b = parseAnnotation(mpath)
try:
b = parseAnnotation(mpath)
except:
print("\tFAILED")
continue
b = b.reindex_like(a, method="ffill")
b = b.fillna(method="bfill")
diff = compare_annotation_df(a, b)
confm = compute_confusion_matrix(a, b)
plt.imshow(confm, cmap="Blues")
Expand Down
342 changes: 245 additions & 97 deletions notebooks/confusion_matrix.ipynb

Large diffs are not rendered by default.

0 comments on commit 6d1fb57

Please sign in to comment.