| Trees | Indices | Help |
|
|---|
|
|
1 """Interactive shell tools for the Halfspan formalism. 2 3 These tools concern song recognition and allow utilities for recognising 4 songs to be called from the shell. 5 6 """ 7 """ 8 ============================== License ======================================== 9 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 10 11 This file is part of The Jazz Parser. 12 13 The Jazz Parser is free software: you can redistribute it and/or modify 14 it under the terms of the GNU General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version. 17 18 The Jazz Parser is distributed in the hope that it will be useful, 19 but WITHOUT ANY WARRANTY; without even the implied warranty of 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 GNU General Public License for more details. 22 23 You should have received a copy of the GNU General Public License 24 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>. 25 26 ============================ End license ====================================== 27 28 """ 29 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>" 30 31 from jazzparser.shell.tools import Tool 32 from jazzparser.shell import ShellError 33 from jazzparser.utils.options import ModuleOption, options_help_text 34 from jazzparser.utils.strings import str_to_bool 3537 """ 38 Tool to load a corpus of tonal space analyses of songs. These may then 39 be used for song recognition. This must be called before other song 40 recognition tools will work. 41 42 A corpus may be created from the chord corpus using the bin/data/parsegs.py 43 to parse the chord corpus and store the analyses in a file. 44 45 """ 46 47 name = "Load analysis set" 48 commands = ['loadsongs'] 49 usage = ('loadsongs <name>', "load the named tonal space analysis corpus") 50 help = """\ 51 Loads a tonal space analysis corpus by name. This corpus may then be used by 52 other tools which require a song corpus. 53 54 These corpora are built using the script bin/data/parsegs.py. 55 """ 567358 from jazzparser.data.tonalspace import TonalSpaceAnalysisSet 59 if len(args) != 1: 60 raise ShellError, "Please give the name of a tonal space analysis "\ 61 "set. Available sets are: %s" % \ 62 ", ".join(TonalSpaceAnalysisSet.list()) 63 64 try: 65 # Try loading the named set 66 songset = TonalSpaceAnalysisSet.load(args[0]) 67 except Exception, err: 68 raise ShellError, "Error loading tonal space analysis set: %s" % \ 69 err 70 print "Loaded tonal space analysis set '%s'" % args[0] 71 # Store this in the state so other tools can use it 72 state.data['songset'] = songset75 name = "List songs" 76 commands = ['songs'] 77 usage = ('songs', "list songs in loaded songset") 78 help = """\ 79 List all the song names in the loaded tonal space analysis songset. 80 """ 818890 name = "Print analysis" 91 commands = ['songanal'] 92 usage = ('songanal <songnum>', "display the tonal space analysis for song "\ 93 "number <songnum> in the loaded songset") 94 help = """\ 95 Prints the tonal space path that is the analysis of a song from a loaded 96 songset. 97 """ 98113100 from jazzparser.formalisms.music_halfspan.semantics import semantics_to_coordinates 101 102 if len(args) == 0: 103 raise ShellError, "Give a song number" 104 # Get the song from the dataset 105 song = get_song(int(args[0]), state) 106 print "Analysis of '%s'" % song[0] 107 print "\nSemantics" 108 # Display the semantics 109 print song[1] 110 print "\nTonal space path" 111 # Also display the TS coordinates 112 print semantics_to_coordinates(song[1])115 name = "Compare result" 116 commands = ['songcomparets', 'songcompts'] 117 usage = ('songcomparets <result-num> <song-num>', "compare a parse result "\ 118 "to a song in the database using the tonal space edit distance metric") 119 help = """\ 120 Compares a parse result to a specific song in the database using the tonal 121 space edit distance metric and outputs the alignment distance. 122 123 See also: 124 songcomparedep: to compare a result to a song in terms of dependency 125 recovery. 126 """ 127 tool_options = Tool.tool_options + [ 128 ModuleOption('local', filter=str_to_bool, 129 usage="local=B, where B is true or false", 130 default=False, 131 help_text="Use local alignment to score the similarity "\ 132 "of the tonal space paths instead of global"), 133 ModuleOption('song', filter=str_to_bool, 134 usage="tosong=B, where B is true or false", 135 default=False, 136 help_text="Compare the numbered song in the corpus to the "\ 137 "second song, instead of comparing the numbered result "\ 138 "to the song"), 139 ModuleOption('alignment', filter=str_to_bool, 140 usage="alignment=B, where B is true or false", 141 default=False, 142 help_text="Output the full alignment, with the two step "\ 143 "lists above one another"), 144 ] 145215147 from jazzparser.formalisms.music_halfspan.evaluation import \ 148 tonal_space_local_alignment, tonal_space_alignment, \ 149 arrange_alignment 150 151 if len(args) < 2: 152 raise ShellError, "Give a result number and a song number" 153 154 resnum = int(args[0]) 155 songnum = int(args[1]) 156 157 song = get_song(songnum, state) 158 songsem = song[1] 159 160 if self.options['song']: 161 # Compare a song instead of a result 162 compsong = get_song(resnum, state) 163 resultsem = compsong[1] 164 print "Comparing '%s' to '%s'" % (compsong[0], song[0]) 165 else: 166 # Normal behaviour: compare a result to a song 167 if resnum >= len(state.results): 168 raise ShellError, "No result number %d" % resnum 169 result = state.results[resnum] 170 resultsem = result.semantics 171 print "Comparing result %d to '%s'" % (resnum, song[0]) 172 173 # Do the comparison 174 if self.options['local']: 175 ops, song_steps, result_steps, distance = \ 176 tonal_space_local_alignment(songsem.lf, resultsem.lf) 177 else: 178 ops, song_steps, result_steps, distance = \ 179 tonal_space_alignment(songsem.lf, resultsem.lf, distance=True) 180 print "Steps in '%s':" % song[0] 181 print song_steps 182 if self.options['song']: 183 print "Steps in '%s'" % compsong[0] 184 else: 185 print "Steps in result path:" 186 print result_steps 187 print "Alignment operations:" 188 print ops 189 190 if self.options['alignment']: 191 print "Full alignment:" 192 # Print the alignment in three rows 193 WRAP_TO = 70 194 wrapped_rows = [] 195 current_row = [] 196 current_width = 0 197 # Wrap the rows 198 for cells in arrange_alignment(song_steps, result_steps, ops): 199 if len(cells[0]) + current_width > WRAP_TO: 200 # Start a new row 201 wrapped_rows.append(current_row) 202 current_row = [] 203 current_width = 0 204 current_row.append(cells) 205 current_width += len(cells[0]) 206 # Add the incomplete last row 207 wrapped_rows.append(current_row) 208 for row in wrapped_rows: 209 lefts, rights, opses = zip(*row) 210 print " ".join(lefts) 211 print " ".join(rights) 212 print " ".join(opses) 213 print 214 print "Distance: %s" % distance217 name = "Compare result" 218 commands = ['songcomparedep', 'songdep'] 219 usage = ('songcomparedep <result-num> <song-num>', "compare a parse result "\ 220 "to a song in the database using the tonal space edit distance metric") 221 help = """\ 222 Compares a parse result to a specific song in the database in terms of 223 dependency recovery and outputs the recall, precision and f-score. 224 225 See also: 226 songcomparets: to compare a result to a song in terms of tonal space path 227 edit distance. 228 """ 229 tool_options = Tool.tool_options + [ 230 ModuleOption('song', filter=str_to_bool, 231 usage="tosong=B, where B is true or false", 232 default=False, 233 help_text="Compare the numbered song in the corpus to the "\ 234 "second song, instead of comparing the numbered result "\ 235 "to the song"), 236 ] 237282239 from jazzparser.formalisms.music_halfspan.semantics.distance import \ 240 MaximalDependencyAlignment 241 242 if len(args) < 2: 243 raise ShellError, "Give a result number and a song number" 244 245 resnum = int(args[0]) 246 songnum = int(args[1]) 247 248 song = get_song(songnum, state) 249 songsem = song[1] 250 251 if self.options['song']: 252 # Compare a song instead of a result 253 compsong = get_song(resnum, state) 254 resultsem = compsong[1] 255 print "Comparing '%s' to '%s'" % (compsong[0], song[0]) 256 else: 257 # Normal behaviour: compare a result to a song 258 if resnum >= len(state.results): 259 raise ShellError, "No result number %d" % resnum 260 result = state.results[resnum] 261 resultsem = result.semantics 262 print "Comparing result %d to '%s'" % (resnum, song[0]) 263 264 # Compare the two logical forms on the basis of overlapping dependencies 265 options = { 266 'output' : 'recall', 267 } 268 recall_metric = MaximalDependencyAlignment(options=options) 269 270 options = { 271 'output' : 'precision', 272 } 273 precision_metric = MaximalDependencyAlignment(options=options) 274 275 recall = recall_metric.distance(resultsem, songsem) 276 precision = precision_metric.distance(resultsem, songsem) 277 278 # Print out each comparison 279 print "Recall: %s" % recall 280 print "Precision: %s" % precision 281 print "F-score: %s" % (2.0*recall*precision / (recall+precision))284 name = "Recognise song" 285 commands = ['findsong', 'song'] 286 usage = ('findsong [<result-num>]', "find the closest matching song "\ 287 "in the loaded songset") 288 help = """\ 289 Compares a parse result (the top probability one by default) to all the songs 290 in the loaded songset and finds the closest matches by tonal space path 291 similarity. Outputs a list of the closest matches. 292 """ 293 tool_options = Tool.tool_options + [ 294 ModuleOption('average', filter=int, 295 usage="average=N, where B is an integer", 296 help_text="Average the distance measure over that given "\ 297 "by the top N results (starting at the result given "\ 298 "in the first argument, if given)"), 299 ModuleOption('metric', 300 usage="metric=M, where M is the name of an available metric", 301 help_text="Select a metric to make the comparison with. "\ 302 "Call with metric=help to get a list of metrics"), 303 ModuleOption('mopts', 304 usage="mopts=OPT=VAL:OPT=VAL:...", 305 help_text="Options to pass to the metric. Use mopts=help "\ 306 "to see a list of options"), 307 ] 308390310 from jazzparser.formalisms.music_halfspan.evaluation import \ 311 tonal_space_local_alignment, tonal_space_distance 312 from jazzparser.formalisms.music_halfspan import Formalism 313 314 metric_name = self.options['metric'] 315 if metric_name == "help": 316 # Print a list of available metrics 317 print ", ".join([metric.name for metric in Formalism.semantics_distance_metrics]) 318 return 319 320 if len(args) == 0: 321 resnum = 0 322 else: 323 resnum = int(args[0]) 324 325 if self.options['average'] and self.options['average'] > 1: 326 # Average the distance over several results 327 resnums = range(resnum, resnum+self.options['average']) 328 else: 329 # Just a single result 330 resnums = [resnum] 331 332 resultsems = [] 333 for resnum in resnums: 334 # Get the result semantics that we're going to try to match 335 if resnum >= len(state.results): 336 raise ShellError, "No result number %d" % resnum 337 result = state.results[resnum] 338 resultsems.append(result.semantics) 339 340 # Get the loaded songset containing the song corpus 341 songset = state.get_data("songset", 342 help_msg="Use command 'loadsongs' to load a songset") 343 344 # Load the appropriate metric 345 if metric_name is None: 346 # Use the first in the list as default 347 metric_cls = Formalism.semantics_distance_metrics[0] 348 else: 349 for m in Formalism.semantics_distance_metrics: 350 if m.name == metric_name: 351 metric_cls = m 352 break 353 else: 354 # No metric found matching this name 355 print "No metric '%s'" % metric_name 356 sys.exit(1) 357 print "Using distance metric: %s\n" % metric_cls.name 358 # Now process the metric options 359 moptstr = self.options['mopts'] 360 if moptstr is not None: 361 if moptstr == "help": 362 # Output this metric's option help 363 print options_help_text(metric_cls.OPTIONS, 364 intro="Available options for metric '%s'" % metric_cls.name) 365 return 366 else: 367 moptstr = "" 368 mopts = ModuleOption.process_option_string(moptstr) 369 # Instantiate the metric with these options 370 metric = metric_cls(options=mopts) 371 372 song_distances = {} 373 # Try matching against each song 374 for resultsem in resultsems: 375 for name,song in songset.analyses: 376 distance = metric.distance(resultsem, song) 377 song_distances.setdefault(name, []).append(distance) 378 # Average the scores 379 distances = [] 380 for name,costs in song_distances.items(): 381 ave_cost = sum(costs)/float(len(costs)) 382 distances.append((ave_cost,name)) 383 384 # Sort so the closest ones come first 385 distances.sort(key=lambda x:x[0]) 386 387 # Output all the songs, ordered by similarity, with their distance 388 for i,(distance,name) in enumerate(distances): 389 print "%d> %s (%s)" % (i, name, distance)392 """ 393 For fooling around with comparing songs to themselves to see what happens. 394 395 """ 396 name = "Self similarity" 397 398 commands = ['selfsim'] 399 usage = ('selfsim <song-num>', "") 400 help = "" 401 tool_options = Tool.tool_options + [ 402 ModuleOption('local', filter=str_to_bool, 403 usage="local=B, where B is true or false", 404 default=False, 405 help_text="Sort results by local alignment score, not "\ 406 "global"), 407 ] 408436 437410 from jazzparser.formalisms.music_halfspan.evaluation import \ 411 tonal_space_local_alignment, tonal_space_distance 412 songnum = int(args[0]) 413 414 name,song = get_song(songnum, state) 415 songset = state.get_data("songset") 416 distances = [] 417 # Try comparing this song to each song in the set 418 for other_name,other_song in songset.analyses: 419 # Align locally and globally 420 ops,steps1,steps2,local_distance = \ 421 tonal_space_local_alignment(other_song.lf, song.lf) 422 global_distance = \ 423 tonal_space_distance(other_song.lf, song.lf) 424 distances.append((other_name, local_distance, global_distance)) 425 426 # Sort the results 427 if self.options['local']: 428 distances.sort(key=lambda x:x[1]) 429 else: 430 distances.sort(key=lambda x:x[2]) 431 # Print out each one 432 print "Aligned %s with:" % name 433 for other_name, local_distance, global_distance in distances: 434 print "%s: local: %s, global: %s" % \ 435 (other_name,local_distance,global_distance)439 """ 440 Converts a song's semantics to a tree. Mainly just for debugging. 441 442 """ 443 name = "Song tree" 444 commands = ['tree'] 445 usage = ('tree <song-num>', "converts the semantics of the song to a tree "\ 446 "representation") 447 tool_options = Tool.tool_options + [ 448 ModuleOption('res', filter=str_to_bool, 449 usage="res=B, where B is true or false", 450 default=False, 451 help_text="Show a result, instead of a corpus song"), 452 ] 453 help = """\ 454 Converts the semantics of the numbered song to its tree representation that 455 will be used for comparison to other logical forms. This is mainly for 456 debugging and has no use in itself. 457 """ 458477460 from jazzparser.formalisms.music_halfspan.harmstruct import \ 461 semantics_to_dependency_trees 462 if self.options['res']: 463 resnum = int(args[0]) 464 res = state.results[resnum] 465 song = res.semantics 466 print "Dependency tree for result %d\n" % resnum 467 else: 468 songnum = int(args[0]) 469 name,song = get_song(songnum, state) 470 print "Dependency tree for '%s'\n" % name 471 472 print "Semantics:" 473 print song 474 print "\nTrees:" 475 for t in semantics_to_dependency_trees(song): 476 print t479 """ 480 Converts a song's semantics to a tree. Mainly just for debugging. 481 482 """ 483 name = "Song dependency graph" 484 commands = ['depgraph', 'dep'] 485 usage = ('depgraph <song-num>', "converts the semantics of the song to a "\ 486 "dependency graph representation") 487 tool_options = Tool.tool_options + [ 488 ModuleOption('res', filter=str_to_bool, 489 usage="res=B, where B is true or false", 490 default=False, 491 help_text="Show a result, instead of a corpus song"), 492 ] 493 help = """\ 494 Converts the semantics of the numbered song to its tree representation that 495 will be used for comparison to other logical forms. This is mainly for 496 debugging and has no use in itself. 497 """ 498517 518500 from jazzparser.formalisms.music_halfspan.harmstruct import \ 501 semantics_to_dependency_graph 502 if self.options['res']: 503 resnum = int(args[0]) 504 res = state.results[resnum] 505 song = res.semantics 506 print "Dependency graph for result %d\n" % resnum 507 else: 508 songnum = int(args[0]) 509 name,song = get_song(songnum, state) 510 print "Dependency graph for '%s'\n" % name 511 512 print "Semantics:" 513 print song 514 print 515 graph, times = semantics_to_dependency_graph(song) 516 print graph520 """ 521 Retreive a song from the loaded songset by number. Utility function used 522 by tools above. 523 524 """ 525 songset = state.get_data("songset", 526 help_msg="Use command 'loadsongs' to load a songset") 527 if num >= len(songset): 528 raise ShellError, "There is no song %d. Use the 'songs' command to "\ 529 "see a list of songs" % num 530 else: 531 return songset.analyses[num]532
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Nov 26 16:05:03 2012 | http://epydoc.sourceforge.net |