diff --git a/docs/examples/example_beat_output.jams b/docs/examples/example_beat_output.jams index 3f0f7fd3..f633c4bd 100644 --- a/docs/examples/example_beat_output.jams +++ b/docs/examples/example_beat_output.jams @@ -1,385 +1,835 @@ { - "sandbox": {}, + "sandbox": {}, + "file_metadata": { + "duration": 61.45886621315193, + "title": "", + "release": "", + "identifiers": {}, + "artist": "", + "jams_version": "0.2.3" + }, "annotations": [ { - "namespace": "beat", - "sandbox": {}, - "time": 0, - "duration": null, + "sandbox": {}, + "duration": null, + "data": [ + { + "value": null, + "confidence": null, + "time": 0.11609977324263039, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 0.5572789115646258, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 0.9984580498866213, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 1.4628571428571429, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 1.9272562358276644, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 2.391655328798186, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 2.8328344671201813, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 3.297233560090703, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 3.7616326530612243, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 4.2260317460317465, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 4.690430839002268, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 5.154829931972789, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 5.61922902494331, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 6.0836281179138325, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 6.524807256235827, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 6.989206349206349, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 7.453605442176871, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 7.918004535147392, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 8.382403628117913, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 8.870022675736962, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 9.311201814058958, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 9.775600907029478, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 10.24, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 10.704399092970522, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 11.145578231292516, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 11.609977324263038, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 12.07437641723356, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 12.538775510204081, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 13.003174603174603, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 13.467573696145125, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 13.931972789115646, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 14.396371882086168, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 14.837551020408164, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 15.27873015873016, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 15.74312925170068, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 16.207528344671204, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 16.671927437641724, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 17.11310657596372, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 17.600725623582765, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 18.04190476190476, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 18.52952380952381, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 18.970702947845805, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 19.435102040816325, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 19.89950113378685, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 20.36390022675737, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 20.805079365079365, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 21.292698412698414, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 21.73387755102041, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 22.221496598639455, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 22.66267573696145, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 23.127074829931974, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 23.591473922902495, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 24.055873015873015, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 24.49705215419501, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 24.961451247165535, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 25.425850340136055, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 25.913469387755104, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 26.354648526077096, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 26.81904761904762, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 27.28344671201814, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 27.74784580498866, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 28.189024943310656, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 28.65342403628118, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 29.1178231292517, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 29.60544217687075, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 30.06984126984127, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 30.53424036281179, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 30.975419501133786, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 31.43981859410431, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 31.880997732426305, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 32.36861678004535, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 32.833015873015874, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 33.29741496598639, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 33.73859410430839, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 34.202993197278914, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 34.66739229024943, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 35.131791383219955, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 35.57297052154195, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 36.060589569160996, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 36.52498866213152, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 36.989387755102044, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 37.430566893424036, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 37.89496598639456, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 38.35936507936508, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 38.8237641723356, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 39.2649433106576, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 39.75256235827664, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 40.216961451247165, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 40.68136054421769, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 41.12253968253968, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 41.586938775510205, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 42.05133786848072, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 42.515736961451246, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 42.956916099773245, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 43.44453514739229, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 43.885714285714286, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 44.373333333333335, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 44.83773242630385, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 45.302131519274376, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 45.7665306122449, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 46.20770975056689, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 46.672108843537416, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 47.13650793650794, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 47.600907029478456, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 48.06530612244898, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 48.529705215419504, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 48.99410430839002, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 49.458503401360545, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 49.92290249433107, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 50.387301587301586, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 50.85170068027211, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 51.2928798185941, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 51.757278911564626, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 52.22167800453515, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 52.68607709750567, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 53.15047619047619, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 53.614875283446715, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 54.05605442176871, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 54.52045351473923, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 54.98485260770975, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 55.44925170068027, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 55.913650793650795, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 56.37804988662131, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 56.842448979591836, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 57.30684807256236, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 57.77124716553288, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 58.2356462585034, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 58.6768253968254, + "duration": 0.0 + }, + { + "value": null, + "confidence": null, + "time": 59.14122448979592, + "duration": 0.0 + } + ], + "namespace": "beat", + "time": 0, "annotation_metadata": { - "annotation_tools": "", + "corpus": "", + "validation": "", + "annotation_tools": "", + "version": "", "curator": { - "name": "", + "name": "", "email": "" - }, - "annotator": {}, - "version": "", - "corpus": "", - "annotation_rules": "", - "validation": "", + }, + "annotation_rules": "", + "annotator": {}, "data_source": "librosa beat tracker" - }, + } + }, + { + "sandbox": {}, + "duration": 61.45886621315193, "data": [ { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 7.430385 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 8.289524 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 9.218322 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 10.1239 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 11.145578 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 12.190476 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 13.212154 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 14.140952 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 15.27873 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 16.207528 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 17.113107 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 18.041905 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 18.970703 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 19.899501 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 20.805079 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 21.733878 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 22.662676 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 23.591474 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 24.497052 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 25.42585 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 26.354649 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 27.283447 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 28.189025 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 29.117823 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 30.069841 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 30.97542 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 31.880998 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 32.833016 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 33.738594 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 34.667392 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 35.572971 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 36.524989 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 37.453787 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 38.359365 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 39.264942 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 40.216961 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 41.14576 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 42.051338 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 42.956916 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 43.885714 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 44.837732 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 45.97551 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 46.904308 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 47.833107 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 48.761905 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 49.667483 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 50.596281 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 51.525078 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 52.453878 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 53.359456 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 54.288254 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 55.217052 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 56.12263 - }, - { - "duration": 0.0, - "confidence": NaN, - "value": NaN, - "time": 57.051429 + "value": 129.19921875, + "confidence": 1.0, + "time": 0.0, + "duration": 61.45886621315193 } - ] - }, - { - "namespace": "tempo", - "sandbox": {}, - "time": 0, - "duration": 61.45886621315193, + ], + "namespace": "tempo", + "time": 0, "annotation_metadata": { - "annotation_tools": "", + "corpus": "", + "validation": "", + "annotation_tools": "", + "version": "", "curator": { - "name": "", + "name": "", "email": "" - }, - "annotator": {}, - "version": "", - "corpus": "", - "annotation_rules": "", - "validation": "", + }, + "annotation_rules": "", + "annotator": {}, "data_source": "librosa tempo estimator" - }, - "data": [ - { - "duration": 61.458866, - "confidence": 1.0, - "value": 64.599609375, - "time": 0.0 - } - ] + } } - ], - "file_metadata": { - "jams_version": "0.2.0", - "title": "", - "identifiers": {}, - "release": "", - "duration": 61.45886621315193, - "artist": "" - } + ] } \ No newline at end of file diff --git a/docs/examples/example_chord.jams b/docs/examples/example_chord.jams index 7f5959d6..5b106559 100644 --- a/docs/examples/example_chord.jams +++ b/docs/examples/example_chord.jams @@ -1,406 +1,406 @@ { - "sandbox": {}, "annotations": [ { - "namespace": "chord", - "sandbox": {}, - "time": 0, - "duration": 175.804082, - "annotation_metadata": { - "annotation_tools": "", - "curator": { - "name": "", - "email": "" - }, - "annotator": {}, - "version": "", - "corpus": "", - "annotation_rules": "", - "validation": "", - "data_source": "" - }, + "duration": 175.804082, "data": [ { - "duration": 2.612267, - "confidence": 1.0, - "value": "N", + "duration": 2.6122669999999997, + "value": "N", + "confidence": 1.0, "time": 0.0 - }, + }, { - "duration": 8.846803, - "confidence": 1.0, - "value": "E", - "time": 2.612267 - }, + "duration": 8.846803000000001, + "value": "E", + "confidence": 1.0, + "time": 2.6122669999999997 + }, { - "duration": 1.462857, - "confidence": 1.0, - "value": "A", + "duration": 1.4628569999999996, + "value": "A", + "confidence": 1.0, "time": 11.45907 - }, + }, { - "duration": 4.521547, - "confidence": 1.0, - "value": "E", + "duration": 4.521546999999998, + "value": "E", + "confidence": 1.0, "time": 12.921927 - }, + }, { - "duration": 2.966888, - "confidence": 1.0, - "value": "B", + "duration": 2.966888000000001, + "value": "B", + "confidence": 1.0, "time": 17.443474 - }, + }, { - "duration": 1.497687, - "confidence": 1.0, - "value": "E", + "duration": 1.497686999999999, + "value": "E", + "confidence": 1.0, "time": 20.410362 - }, + }, { - "duration": 1.462858, - "confidence": 1.0, - "value": "E:7/3", + "duration": 1.4628580000000007, + "value": "E:7/3", + "confidence": 1.0, "time": 21.908049 - }, + }, { - "duration": 1.486077, - "confidence": 1.0, - "value": "A", + "duration": 1.4860770000000016, + "value": "A", + "confidence": 1.0, "time": 23.370907 - }, + }, { - "duration": 1.486077, - "confidence": 1.0, - "value": "A:min/b3", + "duration": 1.486076999999998, + "value": "A:min/b3", + "confidence": 1.0, "time": 24.856984 - }, + }, { - "duration": 1.497687, - "confidence": 1.0, - "value": "E", + "duration": 1.497686999999999, + "value": "E", + "confidence": 1.0, "time": 26.343061 - }, + }, { - "duration": 1.509297, - "confidence": 1.0, - "value": "B", - "time": 27.840748 - }, + "duration": 1.5092970000000037, + "value": "B", + "confidence": 1.0, + "time": 27.840747999999998 + }, { - "duration": 5.955918, - "confidence": 1.0, - "value": "E", + "duration": 5.955917999999997, + "value": "E", + "confidence": 1.0, "time": 29.350045 - }, + }, { - "duration": 1.497687, - "confidence": 1.0, - "value": "A", + "duration": 1.497686999999999, + "value": "A", + "confidence": 1.0, "time": 35.305963 - }, + }, { - "duration": 4.459452, - "confidence": 1.0, - "value": "E", + "duration": 4.459452000000006, + "value": "E", + "confidence": 1.0, "time": 36.80365 - }, + }, { - "duration": 2.982544, - "confidence": 1.0, - "value": "B", + "duration": 2.982543999999997, + "value": "B", + "confidence": 1.0, "time": 41.263102 - }, + }, { - "duration": 1.474467, - "confidence": 1.0, - "value": "E", + "duration": 1.474466999999997, + "value": "E", + "confidence": 1.0, "time": 44.245646 - }, + }, { - "duration": 1.486077, - "confidence": 1.0, - "value": "E:7/3", + "duration": 1.4860770000000016, + "value": "E:7/3", + "confidence": 1.0, "time": 45.720113 - }, + }, { - "duration": 1.486077, - "confidence": 1.0, - "value": "A", + "duration": 1.4860770000000016, + "value": "A", + "confidence": 1.0, "time": 47.20619 - }, + }, { - "duration": 1.462857, - "confidence": 1.0, - "value": "A:min/b3", + "duration": 1.4628569999999996, + "value": "A:min/b3", + "confidence": 1.0, "time": 48.692267 - }, + }, { - "duration": 1.497687, - "confidence": 1.0, - "value": "E", + "duration": 1.497686999999999, + "value": "E", + "confidence": 1.0, "time": 50.155124 - }, + }, { - "duration": 1.486077, - "confidence": 1.0, - "value": "B", + "duration": 1.4860770000000016, + "value": "B", + "confidence": 1.0, "time": 51.652811 - }, + }, { - "duration": 2.972155, - "confidence": 1.0, - "value": "E", + "duration": 2.9721550000000008, + "value": "E", + "confidence": 1.0, "time": 53.138888 - }, + }, { - "duration": 9.020952, - "confidence": 1.0, - "value": "A", + "duration": 9.020951999999987, + "value": "A", + "confidence": 1.0, "time": 56.111043 - }, + }, { - "duration": 3.018594, - "confidence": 1.0, - "value": "B", - "time": 65.131995 - }, + "duration": 3.0185940000000073, + "value": "B", + "confidence": 1.0, + "time": 65.13199499999999 + }, { - "duration": 3.041814, - "confidence": 1.0, - "value": "A", + "duration": 3.0418140000000022, + "value": "A", + "confidence": 1.0, "time": 68.150589 - }, + }, { - "duration": 3.006984, - "confidence": 1.0, - "value": "E", + "duration": 3.0069840000000028, + "value": "E", + "confidence": 1.0, "time": 71.192403 - }, + }, { - "duration": 1.497687, - "confidence": 1.0, - "value": "A", + "duration": 1.497686999999999, + "value": "A", + "confidence": 1.0, "time": 74.199387 - }, + }, { - "duration": 4.539501, - "confidence": 1.0, - "value": "E", + "duration": 4.539501000000001, + "value": "E", + "confidence": 1.0, "time": 75.697074 - }, + }, { - "duration": 2.972155, - "confidence": 1.0, - "value": "B", + "duration": 2.9721550000000008, + "value": "B", + "confidence": 1.0, "time": 80.236575 - }, + }, { - "duration": 3.012963, - "confidence": 1.0, - "value": "E", + "duration": 3.012962999999999, + "value": "E", + "confidence": 1.0, "time": 83.20873 - }, + }, { - "duration": 1.514928, - "confidence": 1.0, - "value": "A", + "duration": 1.5149279999999976, + "value": "A", + "confidence": 1.0, "time": 86.221693 - }, + }, { - "duration": 1.520907, - "confidence": 1.0, - "value": "A:min/b3", + "duration": 1.5209070000000082, + "value": "A:min/b3", + "confidence": 1.0, "time": 87.736621 - }, + }, { - "duration": 1.462857, - "confidence": 1.0, - "value": "E", - "time": 89.257527 - }, + "duration": 1.4628569999999854, + "value": "E", + "confidence": 1.0, + "time": 89.25752800000001 + }, { - "duration": 1.437068, - "confidence": 1.0, - "value": "B", + "duration": 1.4370680000000107, + "value": "B", + "confidence": 1.0, "time": 90.720385 - }, + }, { - "duration": 11.949236, - "confidence": 1.0, - "value": "E", + "duration": 11.949235999999999, + "value": "E", + "confidence": 1.0, "time": 92.157453 - }, + }, { - "duration": 3.018594, - "confidence": 1.0, - "value": "B", + "duration": 3.0185940000000073, + "value": "B", + "confidence": 1.0, "time": 104.106689 - }, + }, { - "duration": 3.053424, - "confidence": 1.0, - "value": "E", - "time": 107.125283 - }, + "duration": 3.0534239999999926, + "value": "E", + "confidence": 1.0, + "time": 107.12528300000001 + }, { - "duration": 2.94538, - "confidence": 1.0, - "value": "A", + "duration": 2.9453800000000143, + "value": "A", + "confidence": 1.0, "time": 110.178707 - }, - { - "duration": 1.489631, - "confidence": 1.0, - "value": "E", - "time": 113.124087 - }, - { - "duration": 1.486077, - "confidence": 1.0, - "value": "B", - "time": 114.613718 - }, - { - "duration": 2.845166, - "confidence": 1.0, - "value": "E", + }, + { + "duration": 1.4896309999999744, + "value": "E", + "confidence": 1.0, + "time": 113.12408700000002 + }, + { + "duration": 1.4860770000000088, + "value": "B", + "confidence": 1.0, + "time": 114.61371799999999 + }, + { + "duration": 2.845166000000006, + "value": "E", + "confidence": 1.0, "time": 116.099795 - }, + }, { - "duration": 9.101501, - "confidence": 1.0, - "value": "A", + "duration": 9.101501000000013, + "value": "A", + "confidence": 1.0, "time": 118.944961 - }, + }, { - "duration": 3.006984, - "confidence": 1.0, - "value": "B", - "time": 128.046462 - }, + "duration": 3.0069839999999886, + "value": "B", + "confidence": 1.0, + "time": 128.04646200000002 + }, { - "duration": 2.983764, - "confidence": 1.0, - "value": "A", + "duration": 2.983764000000008, + "value": "A", + "confidence": 1.0, "time": 131.053446 - }, + }, { - "duration": 3.006985, - "confidence": 1.0, - "value": "E", - "time": 134.03721 - }, + "duration": 3.006984999999986, + "value": "E", + "confidence": 1.0, + "time": 134.03721000000002 + }, { - "duration": 1.431329, - "confidence": 1.0, - "value": "A", + "duration": 1.4313290000000052, + "value": "A", + "confidence": 1.0, "time": 137.044195 - }, + }, { - "duration": 4.582639, - "confidence": 1.0, - "value": "E", + "duration": 4.582638999999972, + "value": "E", + "confidence": 1.0, "time": 138.475524 - }, - { - "duration": 2.983764, - "confidence": 1.0, - "value": "B", - "time": 143.058163 - }, - { - "duration": 1.509297, - "confidence": 1.0, - "value": "E", - "time": 146.041927 - }, - { - "duration": 1.509297, - "confidence": 1.0, - "value": "E:7/3", + }, + { + "duration": 2.9837640000000363, + "value": "B", + "confidence": 1.0, + "time": 143.05816299999998 + }, + { + "duration": 1.5092969999999752, + "value": "E", + "confidence": 1.0, + "time": 146.04192700000002 + }, + { + "duration": 1.5092970000000037, + "value": "E:7/3", + "confidence": 1.0, "time": 147.551224 - }, + }, { - "duration": 1.451247, - "confidence": 1.0, - "value": "A", + "duration": 1.451246999999995, + "value": "A", + "confidence": 1.0, "time": 149.060521 - }, + }, { - "duration": 1.509297, - "confidence": 1.0, - "value": "A:min/b3", + "duration": 1.5092970000000037, + "value": "A:min/b3", + "confidence": 1.0, "time": 150.511768 - }, + }, { - "duration": 1.509297, - "confidence": 1.0, - "value": "E", + "duration": 1.509297000000032, + "value": "E", + "confidence": 1.0, "time": 152.021065 - }, + }, { - "duration": 1.532517, - "confidence": 1.0, - "value": "B", - "time": 153.530362 - }, + "duration": 1.5325169999999844, + "value": "B", + "confidence": 1.0, + "time": 153.53036200000003 + }, { - "duration": 4.469842, - "confidence": 1.0, - "value": "E", + "duration": 4.469842, + "value": "E", + "confidence": 1.0, "time": 155.062879 - }, + }, { - "duration": 1.532517, - "confidence": 1.0, - "value": "B", + "duration": 1.5325169999999844, + "value": "B", + "confidence": 1.0, "time": 159.532721 - }, + }, { - "duration": 4.516281, - "confidence": 1.0, - "value": "E", + "duration": 4.516280999999992, + "value": "E", + "confidence": 1.0, "time": 161.065238 - }, + }, { - "duration": 1.532517, - "confidence": 1.0, - "value": "B", + "duration": 1.5325170000000128, + "value": "B", + "confidence": 1.0, "time": 165.581519 - }, + }, { - "duration": 1.532517, - "confidence": 1.0, - "value": "A", + "duration": 1.5325170000000128, + "value": "A", + "confidence": 1.0, "time": 167.114036 - }, + }, { - "duration": 1.090856, - "confidence": 1.0, - "value": "E", + "duration": 1.0908560000000023, + "value": "E", + "confidence": 1.0, "time": 168.646553 - }, + }, { - "duration": 1.949764, - "confidence": 1.0, - "value": "E:9", + "duration": 1.9497639999999876, + "value": "E:9", + "confidence": 1.0, "time": 169.737409 - }, + }, { - "duration": 4.116909, - "confidence": 1.0, - "value": "N", + "duration": 4.116908999999993, + "value": "N", + "confidence": 1.0, "time": 171.687173 } - ] + ], + "namespace": "chord", + "time": 0, + "annotation_metadata": { + "version": "", + "annotation_tools": "", + "annotator": {}, + "curator": { + "email": "", + "name": "" + }, + "data_source": "", + "corpus": "", + "annotation_rules": "", + "validation": "" + }, + "sandbox": {} } - ], + ], "file_metadata": { - "jams_version": "0.2.0", - "title": "", - "identifiers": {}, - "release": "", - "duration": 175.804082, - "artist": "" - } + "duration": 175.804082, + "jams_version": "0.2.3", + "artist": "", + "identifiers": {}, + "release": "", + "title": "" + }, + "sandbox": {} } \ No newline at end of file diff --git a/docs/examples/example_chord_import.py b/docs/examples/example_chord_import.py index 1687c301..7f290460 100644 --- a/docs/examples/example_chord_import.py +++ b/docs/examples/example_chord_import.py @@ -3,6 +3,7 @@ import jams import sys + def import_chord_jams(infile, outfile): # import_lab returns a new jams object, @@ -10,14 +11,12 @@ def import_chord_jams(infile, outfile): jam, chords = jams.util.import_lab('chord', infile) # Infer the track duration from the end of the last annotation - duration = (chords.data['time'] + chords.data['duration']).max() + duration = max([obs.time + obs.duration for obs in chords]) - # this timing will be in pandas timedelta. - # calling duration.total_seconds() converts to float - jam.file_metadata.duration = duration.total_seconds() + jam.file_metadata.duration = duration chords.time = 0 - chords.duration = duration.total_seconds() + chords.duration = duration # save to disk jam.save(outfile) @@ -27,4 +26,3 @@ def import_chord_jams(infile, outfile): infile, outfile = sys.argv[1:] import_chord_jams(infile, outfile) - diff --git a/jams/__init__.py b/jams/__init__.py index 44fc72c4..c85b0b87 100644 --- a/jams/__init__.py +++ b/jams/__init__.py @@ -1,6 +1,9 @@ #!/usr/bin/env python """Top-level module for JAMS""" +import os +from pkg_resources import resource_filename + # Import the necessary modules from .exceptions import * from . import util @@ -12,15 +15,13 @@ from .core import * from .nsconvert import convert -# Populate the namespace mapping -from pkg_resources import resource_filename +# Populate the namespace mapping for _ in util.find_with_extension(resource_filename(__name__, schema.NS_SCHEMA_DIR), 'json'): schema.add_namespace(_) # Populate local namespaces -import os try: for _ in util.find_with_extension(os.environ['JAMS_SCHEMA_DIR'], 'json'): diff --git a/jams/core.py b/jams/core.py index 289266ba..92ca269d 100644 --- a/jams/core.py +++ b/jams/core.py @@ -26,37 +26,35 @@ AnnotationMetadata Curator Annotation - JamsFrame + Observation Sandbox JObject Observation """ import json -import jsonschema +from collections import namedtuple -import numpy as np -import pandas as pd import os import re -import six import warnings import contextlib import gzip -import copy -import sys -from collections import namedtuple +import six +import numpy as np +import pandas as pd +import jsonschema +from sortedcontainers import SortedListWithKey from decorator import decorator - from .version import version as __VERSION__ from . import schema from .exceptions import JamsError, SchemaError, ParameterError __all__ = ['load', - 'JObject', 'Sandbox', 'JamsFrame', + 'JObject', 'Sandbox', 'Annotation', 'Curator', 'AnnotationMetadata', 'FileMetadata', 'AnnotationArray', 'JAMS', 'Observation'] @@ -145,11 +143,13 @@ def _open(name_or_fdesc, mode='r', fmt='auto'): yield fdesc except KeyError: - raise ParameterError('Unknown JAMS extension format: "{:s}"'.format(ext)) + raise ParameterError('Unknown JAMS extension ' + 'format: "{:s}"'.format(ext)) else: # Don't know how to handle this. Raise a parameter error - raise ParameterError('Invalid filename or descriptor: {:r}'.format(name_or_fdesc)) + raise ParameterError('Invalid filename or ' + 'descriptor: {}'.format(name_or_fdesc)) def load(path_or_file, validate=True, strict=True, fmt='auto'): @@ -432,7 +432,8 @@ def search(self, **kwargs): ------- match : bool `True` if any of the search keys match the specified value, - `False` otherwise, or if the search keys do not exist within the object. + `False` otherwise, or if the search keys do not exist + within the object. Examples -------- @@ -530,251 +531,6 @@ class Sandbox(JObject): pass -class JamsFrame(pd.DataFrame): - '''A data-frame class for JAMS. - - This automates certain niceties, such as timestamp - conversion and serialization. - ''' - - __dense = False - - def __init__(self, data=None, index=None, columns=None, dtype=None): - '''Construct a new JamsFrame object. - - Parameters - ---------- - data - Optional data for the new JamsFrame, in any format supported - by `pandas.DataFrame.__init__`. - - Fields must be `['time', 'duration', 'value', 'confidence']`. - - `time` and `duration` fields must be floating point types, - measured in seconds. - - index - Optional index on `data`. - - columns - These parameters are ignored by JamsFrame, but are allowed - for API compatibility with `pandas.DataFrame`. - - dtype : tuple of types - The first entry corresponds to the `value` field's dtype, - the second corresponds to the `confidence` field. - - These can be obtained for any JAMS namespace by - `jams.schema.get_dtypes(namespace_id)`. - - - See Also - -------- - from_dict - from_dataframe - pandas.DataFrame.__init__ - - ''' - super(JamsFrame, self).__init__(data=data, - index=index, - columns=self.fields()) - - self.time = pd.to_timedelta(self.time, unit='s') - self.duration = pd.to_timedelta(self.duration, unit='s') - if dtype: - self.value = self.value.astype(dtype[0]) - self.confidence = self.value.astype(dtype[1]) - - @property - def dense(self): - '''Boolean to determine whether the encoding is dense or sparse. - - Returns - ------- - dense : bool - `True` if the data should be encoded densely - `False` otherwise - ''' - return self.__dense - - @dense.setter - def dense(self, value): - '''Setter for dense''' - self.__dense = value - - @classmethod - def fields(cls): - '''Fields of a JamsFrame: (time, duration, value, confidence) - - Returns - ------- - fields : list - The only permissible fields for a JamsFrame: - `time`, `duration`, `value`, and `confidence` - ''' - return ['time', 'duration', 'value', 'confidence'] - - @classmethod - def from_dict(cls, *args, **kwargs): - '''Construct a new JamsFrame from a dictionary or list of dictionaries. - - This is analogous to pd.DataFrame.from_dict, except the returned object - has the type `JamsFrame`. - - See Also - -------- - pandas.DataFrame.from_dict - from_dataframe - ''' - new_frame = super(JamsFrame, cls).from_dict(*args, **kwargs) - - return cls.from_dataframe(new_frame) - - @classmethod - def from_dataframe(cls, frame): - '''Convert a pandas DataFrame into a JamsFrame. - - Note: this operation is destructive, in that the input - DataFrame will have its type and data altered. - - Parameters - ---------- - frame : pandas.DataFrame - The input DataFrame. Must have the appropriate JamsFrame fields: - 'time', 'duration', 'value', and 'confidence'. - - 'time' and 'duration' fields should be of type `float` and measured - in seconds. - - Returns - ------- - jams_frame : JamsFrame - The input `frame` modified to form a JamsFrame. - - See Also - -------- - from_dict - ''' - # Encode time properly - frame.time = pd.to_timedelta(frame.time, unit='s') - frame.duration = pd.to_timedelta(frame.duration, unit='s') - - # Properly order the columns - frame = frame[cls.fields()] - - # Clobber the class attribute - frame.__class__ = cls - return frame - - @property - def __json__(self): - '''JSON encoding attribute''' - - def __recursive_simplify(D): - '''A simplifier for nested dictionary structures''' - if isinstance(D, list): - return [__recursive_simplify(Di) for Di in D] - - dict_out = {} - for key, value in six.iteritems(D): - if isinstance(value, dict): - dict_out[key] = __recursive_simplify(value) - else: - dict_out[key] = serialize_obj(value) - return dict_out - - # By default, we'll output a record for each row - # But, if the dense flag is set, we'll output the entire - # table as one object - - orient = 'records' - if self.dense: - orient = 'list' - - return __recursive_simplify(self.to_dict(orient=orient)) - - def add_observation(self, time=None, duration=None, - value=None, confidence=None): - '''Add a single observation event to an existing frame. - - New observations are appended to the end of the frame. - - Parameters - ---------- - time : float - The time of the new observation, in seconds - - duration : float - The duration of the new observation, in seconds - - value - confidence - The value and confidence fields of the new observation. - This should conform to the corresponding `namespace` of the - containing `Annotation` object. - - Examples - -------- - >>> frame = jams.JamsFrame() - >>> frame.add_observation(time=3, duration=1.5, value='C#') - >>> frame.add_observation(time=5, duration=.5, value='C#:min', confidence=.8) - >>> frame - time duration value confidence - 0 00:00:03 00:00:01.500000 C# NaN - 1 00:00:05 00:00:00.500000 C#:min 0.8 - ''' - - if time is None or not (time >= 0.0): - raise ParameterError('time={} must be a non-negative number'.format(time)) - - if duration is None or not (duration >= 0.0): - raise ParameterError('duration={} must be a non-negative number'.format(duration)) - - if not len(self): - n = 0 - else: - n = self.index.max() + 1 - - try: - self.set_value(n, 'time', pd.to_timedelta(time, unit='s')) - self.set_value(n, 'duration', pd.to_timedelta(duration, unit='s')) - self.set_value(n, 'value', value) - self.set_value(n, 'confidence', confidence) - except ValueError as exc: - self.drop(n, inplace=True, errors='ignore') - six.reraise(SchemaError, SchemaError(str(exc)), sys.exc_info()[2]) - - @deprecated('0.2.3', '0.3.0') - def to_interval_values(self): - '''Extract observation data in a `mir_eval`-friendly format. - - Returns - ------- - intervals : np.ndarray [shape=(n, 2), dtype=float] - Start- and end-times of all valued intervals - - `intervals[i, :] = [time[i], time[i] + duration[i]]` - - labels : list - List view of value field. - ''' - - times = timedelta_to_float(self.time.values) - duration = timedelta_to_float(self.duration.values) - - return np.vstack([times, times + duration]).T, list(self.value) - - def __deepcopy__(self, memo): - '''Explicit deep-copy implementation''' - jf = JamsFrame() - for field in self.fields(): - jf[field] = pd.Series([copy.deepcopy(_) for _ in self[field]], - dtype=self[field].dtype) - - jf.dense = copy.deepcopy(self.dense) - return jf - - class Annotation(JObject): """Annotation base class.""" @@ -791,9 +547,8 @@ def __init__(self, namespace, data=None, annotation_metadata=None, namespace : str The namespace for this annotation - data : dict or list-of-dict - Data for the new annotation in a format supported by - `JamsFrame.from_dict` + data : dict of lists, list of dicts, or list of Observations + Data for the new annotation annotation_metadata : AnnotationMetadata (or dict), default=None. Metadata corresponding to this Annotation. @@ -817,25 +572,23 @@ def __init__(self, namespace, data=None, annotation_metadata=None, self.namespace = namespace - dtypes = schema.get_dtypes(self.namespace) + self.data = SortedListWithKey(key=self._key) - if data is None: - self.data = JamsFrame(dtype=dtypes) - else: - self.data = JamsFrame.from_dict(data) + if data is not None: + if isinstance(data, dict): + self.append_columns(data) + else: + self.append_records(data) if sandbox is None: sandbox = Sandbox() self.sandbox = Sandbox(**sandbox) - # Set the data export coding to match the namespace - self.data.dense = schema.is_dense(self.namespace) - self.time = time self.duration = duration - def append(self, **kwargs): + def append(self, time=None, duration=None, value=None, confidence=None): '''Append an observation to the data field Parameters @@ -850,41 +603,51 @@ def append(self, **kwargs): Types and values should conform to the namespace of the Annotation object. - See Also - -------- - JamsFrame.add_observation - Examples -------- >>> ann = jams.Annotation(namespace='chord') - >>> ann.append(time=0, duration=3, value='C#') >>> ann.append(time=3, duration=2, value='E#') - >>> ann - - >>> ann.data - time duration value confidence - 0 00:00:00 00:00:03 C# None - 1 00:00:03 00:00:02 E# None ''' - self.data.add_observation(**kwargs) + self.data.add(Observation(time=time, + duration=duration, + value=value, + confidence=confidence)) - def __eq__(self, other): - '''Override JObject equality to handle JamsFrames specially''' - if not isinstance(other, self.__class__): - return False + def append_records(self, records): + '''Add observations from row-major storage. + + This is primarily useful for deserializing sparsely packed data. - for key in self.__dict__: - value = True - if key == 'data': - value = self.__dict__[key].equals(other.__dict__[key]) + Parameters + ---------- + records : iterable of dicts or Observations + Each element of `records` corresponds to one observation. + ''' + for obs in records: + if isinstance(obs, Observation): + self.append(**obs._asdict()) else: - value = self.__dict__[key] == other.__dict__[key] + self.append(**obs) - if not value: - return False + def append_columns(self, columns): + '''Add observations from column-major storage. - return True + This is primarily used for deserializing densely packed data. + + Parameters + ---------- + columns : dict of lists + Keys must be `time, duration, value, confidence`, + and each much be a list of equal length. + + ''' + self.append_records([dict(time=t, duration=d, value=v, confidence=c) + for (t, d, v, c) + in six.moves.zip(columns['time'], + columns['duration'], + columns['value'], + columns['confidence'])]) def validate(self, strict=True): '''Validate this annotation object against the JAMS schema, @@ -900,7 +663,8 @@ def validate(self, strict=True): ------- valid : bool `True` if the object conforms to schema. - `False` if the object fails to conform to schema, but `strict == False`. + `False` if the object fails to conform to schema, + but `strict == False`. Raises ------ @@ -918,18 +682,9 @@ def validate(self, strict=True): ann_schema = schema.namespace(self.namespace) try: - records = self.data.__json__ - - # If the data has a dense packing, reshape it for record-wise - # validation - if self.data.dense: - records = [dict(_) - for _ in zip(*[[(k, v) for v in value] - for (k, value) in six.iteritems(records)])] - # validate each record in the frame - for rec in records: - jsonschema.validate(rec, ann_schema) + for rec in self.data: + jsonschema.validate(serialize_obj(rec), ann_schema) except jsonschema.ValidationError as invalid: if strict: @@ -1011,19 +766,17 @@ def trim(self, start_time, end_time, strict=False): >>> ann_trim = ann.trim(5, 8, strict=False) >>> print(ann_trim.time, ann_trim.duration) (5, 3) - >>> ann_trim.data - time duration value confidence - 0 00:00:05 00:00:01 two None - 1 00:00:06 00:00:02 three None - 2 00:00:07 00:00:01 four None - >>> + >>> ann_trim.to_dataframe() + time duration value confidence + 0 5 1 two None + 1 6 2 three None + 2 7 1 four None >>> ann_trim_strict = ann.trim(5, 8, strict=True) >>> print(ann_trim_strict.time, ann_trim_strict.duration) (5, 3) >>> ann_trim_strict.data - time duration value confidence - 0 00:00:06 00:00:02 three None - + time duration value confidence + 0 6 2 three None ''' # Check for basic start_time and end_time validity if end_time <= start_time: @@ -1036,9 +789,9 @@ def trim(self, start_time, end_time, strict=False): orig_time = start_time orig_duration = end_time - start_time warnings.warn( - "Annotation.duration is not defined, cannot check for temporal " - "intersection, assuming the annotation is valid between " - "start_time and end_time.") + "Annotation.duration is not defined, cannot check " + "for temporal intersection, assuming the annotation " + "is valid between start_time and end_time.") else: orig_time = self.time orig_duration = self.duration @@ -1071,10 +824,10 @@ def trim(self, start_time, end_time, strict=False): # We do this rather than copying and directly manipulating the # annotation' data frame (which might be faster) since this way trim is # independent of the internal data representation. - for idx, obs in self.data.iterrows(): + for obs in self.data: - obs_start = obs['time'].total_seconds() - obs_end = obs_start + obs['duration'].total_seconds() + obs_start = obs.time + obs_end = obs_start + obs.duration if obs_start < trim_end and obs_end > trim_start: @@ -1086,10 +839,9 @@ def trim(self, start_time, end_time, strict=False): (new_start == obs_start and new_end == obs_end)): ann_trimmed.append(time=new_start, duration=new_duration, - value=obs['value'], - confidence=obs['confidence']) + value=obs.value, + confidence=obs.confidence) - ann_trimmed.data.reset_index(drop=True, inplace=True) if 'trim' not in ann_trimmed.sandbox.keys(): ann_trimmed.sandbox.update( trim=[{'start_time': start_time, 'end_time': end_time, @@ -1163,31 +915,36 @@ def slice(self, start_time, end_time, strict=False): >>> print(ann_slice.time, ann_slice.duration) (0, 3) >>> ann_slice.data - time duration value confidence - 0 00:00:00 00:00:01 two None - 1 00:00:01 00:00:02 three None - 2 00:00:02 00:00:01 four None - >>> + time duration value confidence + 0 0 1 two None + 1 1 2 three None + 2 2 1 four None >>> ann_slice_strict = ann.slice(5, 8, strict=True) >>> print(ann_slice_strict.time, ann_slice_strict.duration) (0, 3) >>> ann_slice_strict.data - time duration value confidence - 0 00:00:01 00:00:02 three None - + time duration value confidence + 0 1 2 three None ''' # start by trimming the annotation sliced_ann = self.trim(start_time, end_time, strict=strict) + raw_data = sliced_ann.pop_data() # now adjust the start time of the annotation and the observations it # contains. - ref_time = sliced_ann.time - sliced_ann.time = max(0, sliced_ann.time - start_time) - adjustment = ref_time - sliced_ann.time - sliced_ann.data['time'] = sliced_ann.data['time'].apply( - lambda x: x - pd.to_timedelta(adjustment, unit='s')) + for obs in raw_data: + new_time = max(0, obs.time - start_time) + # if obs.time > start_time, + # duration doesn't change + # if obs.time < start_time, + # duration shrinks by start_time - obs.time + sliced_ann.append(time=new_time, + duration=obs.duration, + value=obs.value, + confidence=obs.confidence) + ref_time = sliced_ann.time slice_start = ref_time slice_end = ref_time + sliced_ann.duration @@ -1202,6 +959,19 @@ def slice(self, start_time, end_time, strict=False): return sliced_ann + def pop_data(self): + '''Replace this observation's data with a fresh container. + + Returns + ------- + annotation_data : SortedListWithKey + The original annotation data container + ''' + + data = self.data + self.data = SortedListWithKey(key=self._key) + return data + def to_interval_values(self): '''Extract observation data in a `mir_eval`-friendly format. @@ -1216,19 +986,134 @@ def to_interval_values(self): List view of value field. ''' - times = timedelta_to_float(self.data.time.values) - duration = timedelta_to_float(self.data.duration.values) + ints, vals = [], [] + for obs in self.data: + ints.append([obs.time, obs.time + obs.duration]) + vals.append(obs.value) + + if not ints: + return np.empty(shape=(0, 2), dtype=float), [] + + return np.array(ints), vals - return np.vstack([times, times + duration]).T, list(self.data.value) + def to_event_values(self): + '''Extract observation data in a `mir_eval`-friendly format. - def __iter_obs__(self): - for _, (t, d, v, c) in self.data.iterrows(): - yield Observation(time=t.total_seconds(), - duration=d.total_seconds(), - value=v, confidence=c) + Returns + ------- + times : np.ndarray [shape=(n,), dtype=float] + Start-time of all observations + + labels : list + List view of value field. + ''' + ints, vals = [], [] + for obs in self.data: + ints.append(obs.time) + vals.append(obs.value) + + return np.array(ints), vals + + def to_dataframe(self): + '''Convert this annotation to a pandas dataframe. + + Returns + ------- + df : pd.DataFrame + Columns are `time, duration, value, confidence`. + Each row is an observation, and rows are sorted by + ascending `time`. + ''' + return pd.DataFrame.from_records(list(self.data), + columns=['time', 'duration', + 'value', 'confidence']) def __iter__(self): - return self.__iter_obs__() + return iter(self.data) + + def to_html(self): + '''Render this annotation list in HTML + + Returns + ------- + rendered : str + An HTML table containing this annotation's data. + ''' + out = r''' + + + + + + + + + ''' + out += r'''''' + for i, obs in enumerate(self.data): + out += r''' + + + + + + '''.format(i, + obs.time, + obs.duration, + obs.value, + obs.confidence) + out += r'''
timedurationvalueconfidence
{:d}{:0.6f}{:0.6f}{:}{:}
''' + return out + + def _repr_html_(self): + '''Render annotation as HTML. See also: `to_html()`''' + return self.to_html() + + @property + def __json__(self): + r"""Return the JObject as a set of native data types for serialization. + + Note: attributes beginning with underscores are suppressed. + """ + filtered_dict = dict() + + for k, item in six.iteritems(self.__dict__): + if k.startswith('_'): + continue + elif k == 'data': + filtered_dict[k] = self.__json_data__ + + elif hasattr(item, '__json__'): + filtered_dict[k] = item.__json__ + else: + filtered_dict[k] = item + + return filtered_dict + + @property + def __json_data__(self): + r"""JSON-serialize the observation sequence.""" + if schema.is_dense(self.namespace): + dense_records = dict() + for field in Observation._fields: + dense_records[field] = [] + + for obs in self.data: + for key, val in six.iteritems(obs._asdict()): + dense_records[key].append(serialize_obj(val)) + + return dense_records + + else: + return [serialize_obj(_) for _ in self.data] + + @classmethod + def _key(cls, obs): + '''Provides sorting index for Observation objects''' + if not isinstance(obs, Observation): + raise JamsError('{} must be of type jams.Observation'.format(obs)) + + return obs.time class Curator(JObject): @@ -1362,7 +1247,8 @@ class AnnotationArray(list): are supported: - integer or slice : acts just as in `list`, e.g., `arr[0]` or `arr[1:3]` - - string : acts like a search, e.g., `arr['beat'] == arr.search(namespace='beat')` + - string : acts like a search, e.g., + `arr['beat'] == arr.search(namespace='beat')` - (string, integer or slice) acts like a search followed by index/slice Examples @@ -1847,13 +1733,6 @@ def slice(self, start_time, end_time, strict=False): # -- Helper functions -- # - -def timedelta_to_float(t): - '''Convert a timedelta64[ns] to floating point (seconds)''' - - return t.astype(np.float) * 1e-9 - - def query_pop(query, prefix, sep='.'): '''Pop a prefix from a query string. @@ -1918,7 +1797,8 @@ def match_query(string, query): if six.callable(query): return query(string) - elif isinstance(query, six.string_types) and isinstance(string, six.string_types): + elif (isinstance(query, six.string_types) and + isinstance(string, six.string_types)): return re.match(query, string) is not None else: @@ -1928,20 +1808,18 @@ def match_query(string, query): def serialize_obj(obj): '''Custom serialization functionality for working with advanced data types. - - Timedelta objects are converted to floats (in seconds) - numpy arrays are converted to lists - lists are recursively serialized element-wise ''' - if isinstance(obj, pd.Timedelta): - return obj.total_seconds() - elif isinstance(obj, np.ndarray): + if isinstance(obj, np.ndarray): return obj.tolist() elif isinstance(obj, list): return [serialize_obj(x) for x in obj] - return obj - + elif isinstance(obj, Observation): + return {k: serialize_obj(v) for k, v in six.iteritems(obj._asdict())} + return obj diff --git a/jams/display.py b/jams/display.py index c8161860..ca46aa53 100644 --- a/jams/display.py +++ b/jams/display.py @@ -88,7 +88,7 @@ def pitch_contour(annotation, **kwargs): indices = np.unique([v['index'] for v in values]) for idx in indices: - rows = annotation.data.value.apply(lambda x: x['index'] == idx).nonzero()[0] + rows = [i for (i, v) in enumerate(values) if v['index'] == idx] freqs = np.asarray([values[r]['frequency'] for r in rows]) unvoiced = ~np.asarray([values[r]['voiced'] for r in rows]) freqs[unvoiced] *= -1 diff --git a/jams/eval.py b/jams/eval.py index 7871e0b1..e5adc409 100644 --- a/jams/eval.py +++ b/jams/eval.py @@ -26,7 +26,9 @@ from .nsconvert import convert -__all__ = ['beat', 'chord', 'melody', 'onset', 'segment', 'hierarchy', 'tempo', 'pattern', 'transcription'] +__all__ = ['beat', 'chord', 'melody', 'onset', + 'segment', 'hierarchy', 'tempo', + 'pattern', 'transcription'] def coerce_annotation(ann, namespace): @@ -104,10 +106,11 @@ def beat(ref, est, **kwargs): namespace = 'beat' ref = coerce_annotation(ref, namespace) est = coerce_annotation(est, namespace) - ref_interval, _ = ref.to_interval_values() - est_interval, _ = est.to_interval_values() - return mir_eval.beat.evaluate(ref_interval[:, 0], est_interval[:, 0], **kwargs) + ref_times, _ = ref.to_event_values() + est_times, _ = est.to_event_values() + + return mir_eval.beat.evaluate(ref_times, est_times, **kwargs) def onset(ref, est, **kwargs): @@ -145,10 +148,11 @@ def onset(ref, est, **kwargs): namespace = 'onset' ref = coerce_annotation(ref, namespace) est = coerce_annotation(est, namespace) - ref_interval, _ = ref.to_interval_values() - est_interval, _ = est.to_interval_values() - return mir_eval.onset.evaluate(ref_interval[:, 0], est_interval[:, 0], **kwargs) + ref_times, _ = ref.to_event_values() + est_times, _ = est.to_event_values() + + return mir_eval.onset.evaluate(ref_times, est_times, **kwargs) def chord(ref, est, **kwargs): @@ -350,9 +354,10 @@ def tempo(ref, est, **kwargs): ref = coerce_annotation(ref, 'tempo') est = coerce_annotation(est, 'tempo') - ref_tempi = np.asarray(ref.data['value'].values, dtype=np.float) - ref_weight = float(ref.data['confidence'][0]) - est_tempi = np.asarray(est.data['value'].values, dtype=np.float) + + ref_tempi = np.asarray([o.value for o in ref]) + ref_weight = ref.data[0].confidence + est_tempi = np.asarray([o.value for o in est]) return mir_eval.tempo.evaluate(ref_tempi, ref_weight, est_tempi, **kwargs) @@ -394,14 +399,15 @@ def melody(ref, est, **kwargs): namespace = 'pitch_contour' ref = coerce_annotation(ref, namespace) est = coerce_annotation(est, namespace) - ref_interval, ref_p = ref.to_interval_values() - est_interval, est_p = est.to_interval_values() + + ref_times, ref_p = ref.to_event_values() + est_times, est_p = est.to_event_values() ref_freq = np.asarray([p['frequency'] * (-1)**(~p['voiced']) for p in ref_p]) est_freq = np.asarray([p['frequency'] * (-1)**(~p['voiced']) for p in est_p]) - return mir_eval.melody.evaluate(ref_interval[:, 0], ref_freq, - est_interval[:, 0], est_freq, + return mir_eval.melody.evaluate(ref_times, ref_freq, + est_times, est_freq, **kwargs) @@ -432,11 +438,12 @@ def pattern_to_mireval(ann): patterns = defaultdict(lambda: defaultdict(list)) # Iterate over the data in interval-value format - for interval, observation in zip(*ann.to_interval_values()): + + for time, observation in zip(*ann.to_event_values()): pattern_id = observation['pattern_id'] occurrence_id = observation['occurrence_id'] - obs = (interval[0], observation['midi_pitch']) + obs = (time, observation['midi_pitch']) # Push this note observation into the correct pattern/occurrence patterns[pattern_id][occurrence_id].append(obs) diff --git a/jams/exceptions.py b/jams/exceptions.py index 5b1ec336..8d9aeb70 100644 --- a/jams/exceptions.py +++ b/jams/exceptions.py @@ -2,6 +2,7 @@ # -*- encoding: utf-8 -*- '''Exception classes for JAMS''' + class JamsError(Exception): '''The root JAMS exception class''' pass @@ -11,10 +12,12 @@ class SchemaError(JamsError): '''Exceptions relating to schema validation''' pass + class NamespaceError(JamsError): '''Exceptions relating to task namespaces''' pass + class ParameterError(JamsError): '''Exceptions relating to function and method parameters''' pass diff --git a/jams/nsconvert.py b/jams/nsconvert.py index 8fb033b4..b5d1c8e0 100644 --- a/jams/nsconvert.py +++ b/jams/nsconvert.py @@ -68,7 +68,7 @@ def convert(annotation, target_namespace): ------ SchemaError if the input annotation fails to validate - + NamespaceError if no conversion is possible @@ -142,8 +142,14 @@ def can_convert(annotation, target_namespace): def pitch_hz_to_contour(annotation): '''Convert a pitch_hz annotation to a contour''' annotation.namespace = 'pitch_contour' - annotation.data.value = [dict(index=0, frequency=np.abs(f), voiced=f > 0) - for f in annotation.data.value] + data = annotation.pop_data() + + for obs in data: + annotation.append(time=obs.time, duration=obs.duration, + confidence=obs.confidence, + value=dict(index=0, + frequency=np.abs(obs.value), + voiced=obs.value > 0)) return annotation @@ -159,7 +165,13 @@ def note_midi_to_hz(annotation): '''Convert a pitch_midi annotation to pitch_hz''' annotation.namespace = 'note_hz' - annotation.data.value = 440 * (2.0 ** ((annotation.data.value - 69.0)/12.0)) + data = annotation.pop_data() + + for obs in data: + annotation.append(time=obs.time, duration=obs.duration, + confidence=obs.confidence, + value=440 * (2.0**((obs.value - 69.0)/12.0))) + return annotation @@ -169,8 +181,13 @@ def note_hz_to_midi(annotation): annotation.namespace = 'note_midi' - logf = np.log2(np.asarray(annotation.data.value.values, dtype=np.float)) - annotation.data.value = 12 * (logf - np.log2(440.0)) + 69 + data = annotation.pop_data() + + for obs in data: + annotation.append(time=obs.time, duration=obs.duration, + confidence=obs.confidence, + value=12 * (np.log2(obs.value) - np.log2(440.0)) + 69) + return annotation @@ -179,7 +196,14 @@ def pitch_midi_to_hz(annotation): '''Convert a pitch_midi annotation to pitch_hz''' annotation.namespace = 'pitch_hz' - annotation.data.value = 440 * (2.0 ** ((annotation.data.value - 69.0)/12.0)) + + data = annotation.pop_data() + + for obs in data: + annotation.append(time=obs.time, duration=obs.duration, + confidence=obs.confidence, + value=440 * (2.0**((obs.value - 69.0)/12.0))) + return annotation @@ -188,8 +212,13 @@ def pitch_hz_to_midi(annotation): '''Convert a pitch_hz annotation to pitch_midi''' annotation.namespace = 'pitch_midi' - logf = np.log2(np.asarray(annotation.data.value.values, dtype=np.float)) - annotation.data.value = 12 * (logf - np.log2(440.0)) + 69 + + data = annotation.pop_data() + + for obs in data: + annotation.append(time=obs.time, duration=obs.duration, + confidence=obs.confidence, + value=12 * (np.log2(obs.value) - np.log2(440.0)) + 69) return annotation @@ -214,9 +243,15 @@ def beat_position(annotation): '''Convert beat_position to beat''' annotation.namespace = 'beat' - annotation.data.value = annotation.data.value.apply(lambda x: x['position']) + data = annotation.pop_data() + for obs in data: + annotation.append(time=obs.time, duration=obs.duration, + confidence=obs.confidence, + value=obs.value['position']) + return annotation + @_conversion('chord', 'chord_harte') def chordh_to_chord(annotation): '''Convert Harte annotation to chord''' diff --git a/jams/schema.py b/jams/schema.py index 4203a438..203be0db 100644 --- a/jams/schema.py +++ b/jams/schema.py @@ -126,7 +126,8 @@ def values(ns_key): def get_dtypes(ns_key): - '''Get the dtypes associated with the value and confidence fields for a given schema. + '''Get the dtypes associated with the value and confidence fields + for a given namespace. Parameters ---------- @@ -136,7 +137,7 @@ def get_dtypes(ns_key): Returns ------- value_dtype, confidence_dtype : numpy.dtype - Type identifiers for dataframe/jamsframe columns. + Type identifiers for value and confidence fields. ''' # First, get the schema diff --git a/jams/sonify.py b/jams/sonify.py index a2697c67..bfe1a44f 100644 --- a/jams/sonify.py +++ b/jams/sonify.py @@ -133,7 +133,7 @@ def pitch_contour(annotation, sr=22050, length=None, **kwargs): y_out = 0.0 for ix in indices: - rows = annotation.data.value.apply(lambda x: x['index'] == ix).nonzero()[0] + rows = [i for (i, v) in enumerate(values) if v['index'] == ix] freqs = np.asarray([values[r]['frequency'] for r in rows]) unv = ~np.asarray([values[r]['voiced'] for r in rows]) @@ -153,7 +153,7 @@ def pitch_contour(annotation, sr=22050, length=None, **kwargs): def piano_roll(annotation, sr=22050, length=None, **kwargs): '''Sonify a piano-roll - + This uses mir_eval.sonify.time_frequency, and is appropriate for sparse transcription data, e.g., annotations in the `note_midi` namespace. diff --git a/jams/util.py b/jams/util.py index 47b4d940..6d754b4d 100644 --- a/jams/util.py +++ b/jams/util.py @@ -12,7 +12,6 @@ smkdirs filebase find_with_extension - _deprecated """ import os @@ -116,10 +115,10 @@ def import_lab(namespace, filename, jam=None, infer_duration=True, **parse_optio value = [x for x in row[3:] if x is not None][-1] - annotation.data.add_observation(time=time, - duration=duration, - confidence=1.0, - value=value) + annotation.append(time=time, + duration=duration, + confidence=1.0, + value=value) jam.annotations.append(annotation) diff --git a/setup.py b/setup.py index 8ee80d38..28dc2fe6 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ license='ISC', install_requires=[ 'pandas', + 'sortedcontainers', 'jsonschema', 'numpy>=1.8.0', 'six', diff --git a/tests/eval_test.py b/tests/eval_test.py index f4e0bb26..df30eaaf 100644 --- a/tests/eval_test.py +++ b/tests/eval_test.py @@ -3,7 +3,7 @@ '''mir_eval integration tests''' import numpy as np -from nose.tools import raises, nottest +from nose.tools import raises import jams from util_test import srand @@ -28,7 +28,6 @@ def create_annotation(values, namespace='beat', offset=0.0, duration=1, confiden return ann -@nottest # Temporarily disabled due to mir_eval bug with numpy 1.12 def test_beat_valid(): ref_ann = create_annotation(values=np.arange(10) % 4 + 0.5, @@ -189,7 +188,6 @@ def test_melody_valid(): # Temporarily disabling because pandas 0.20 won't allow us to # construct ill-typed observations -@nottest def test_melody_invalid(): srand() @@ -308,7 +306,6 @@ def test_transcription_valid(): # Temporarily disabled because pandas 0.20 will not allow unsafe type mixtures, # so there's no easy way to make a bad corner case -@nottest def test_transcription_invalid(): ref_jam = jams.load('fixtures/transcription_ref.jams') diff --git a/tests/jams_test.py b/tests/jams_test.py index 9d116a9f..7070dfc2 100644 --- a/tests/jams_test.py +++ b/tests/jams_test.py @@ -9,9 +9,8 @@ import six import sys import warnings -import numpy as np -import pandas as pd +import numpy as np from nose.tools import raises, eq_ try: import pandas.testing as pdt @@ -55,6 +54,9 @@ def test_jobject_serialize(): J = jams.JObject(**data) + # Stick a dummy _value in for testing + J._dummy = True + json_jobject = J.dumps(indent=2) # De-serialize into dicts @@ -131,124 +133,6 @@ def test_sandbox_contains(): assert key in S -# JamsFrame - -def test_jamsframe_fields(): - - eq_(jams.JamsFrame.fields(), ['time', 'duration', 'value', 'confidence']) - - -def test_jamsframe_from_df(): - - df = pd.DataFrame(data=[[0.0, 1.0, 'a', 0.0], - [1.0, 2.0, 'b', 0.0]], - columns=['time', 'duration', 'value', 'confidence']) - - jf = jams.JamsFrame.from_dataframe(df) - - # 1. type check - assert isinstance(jf, jams.JamsFrame) - - # 2. check field order - eq_(list(jf.keys().values), - jams.JamsFrame.fields()) - - # 3. check field types - assert jf['time'].dtype == np.dtype(' 0 - assert out[0].category is DeprecationWarning - assert 'deprecated' in str(out[0].message).lower() - - assert np.allclose(intervals, np.array([[0.0, 1.0], [1.0, 3.0]])) - eq_(values, ['a', 'b']) - - -def test_jamsframe_serialize(): - - def __test(dense, data): - df = pd.DataFrame(data=data, - columns=['time', 'duration', 'value', 'confidence']) - - jf = jams.JamsFrame.from_dataframe(df) - jf.dense = dense - - jf_s = jf.__json__ - - jf2 = jams.JamsFrame.from_dict(jf_s) - - - for key in jams.JamsFrame.fields(): - eq_(list(jf[key]), list(jf2[key])) - - values = [['a', 'b'], [dict(a=1), dict(b=2)]] - - for value in values: - data = [[0.0, 1.0, value[0], 0.0], - [1.0, 2.0, value[1], 0.0]] - for dense in [False, True]: - yield __test, dense, data - - # Curator def test_curator(): @@ -314,14 +198,15 @@ def __test(namespace, data, amd, sandbox): eq_(dict(sandbox), dict(ann.sandbox)) if data is not None: - assert ann.data.equals(jams.JamsFrame.from_dict(data)) + eq_(len(ann.data), len(data)) + for obs1, obs2 in zip(ann.data, data): + eq_(obs1._asdict(), obs2) real_sandbox = jams.Sandbox(description='none') real_amd = jams.AnnotationMetadata(corpus='test collection') - real_data = dict(time=[0.0, 1.0], - duration=[0.5, 0.5], - value=['one', 'two'], - confidence=[0.9, 0.9]) + + real_data = [dict(time=0, duration=0.5, value='one', confidence=0.9), + dict(time=1.0, duration=0.5, value='two', confidence=0.9)] namespace = 'tag_open' @@ -333,10 +218,8 @@ def __test(namespace, data, amd, sandbox): def test_annotation_append(): - data = dict(time=[0.0, 1.0], - duration=[0.5, 0.5], - value=['one', 'two'], - confidence=[0.9, 0.9]) + data = [dict(time=0, duration=0.5, value='one', confidence=0.9), + dict(time=1.0, duration=0.5, value='two', confidence=0.9)] namespace = 'tag_open' @@ -346,10 +229,7 @@ def test_annotation_append(): ann.append(**update) - jf = jams.JamsFrame.from_dict(data) - jf.add_observation(**update) - - assert ann.data.equals(jf) + eq_(ann.data[-1]._asdict(), update) def test_annotation_eq(): @@ -406,6 +286,16 @@ def test_annotation_interval_values(): # FileMetadata + +@raises(jams.JamsError) +def test_annotation_badtype(): + + an = jams.Annotation(namespace='tag_open') + # This should throw a jams error because NoneType can't be indexed + an.data.add(None) + + +# FileMetadata def test_filemetadata(): meta = dict(title='Test track', @@ -418,8 +308,8 @@ def test_filemetadata(): for k in meta: eq_(meta[k], dict_fm[k]) -# AnnotationArray +# AnnotationArray def test_annotation_array(): arr = jams.AnnotationArray() @@ -442,7 +332,7 @@ def test_annotation_array_data(): eq_(len(arr), 3) for t_ann in arr: - assert ann.data.equals(t_ann.data) + eq_(ann.data, t_ann.data) def test_annotation_array_serialize(): @@ -478,6 +368,7 @@ def test_annotation_array_index_simple(): a1, a2 = anns[i], jam.annotations[i] eq_(a1, a2) + def test_annotation_array_slice_simple(): jam = jams.JAMS() @@ -491,6 +382,7 @@ def test_annotation_array_slice_simple(): eq_(len(res), 3) assert anns[0] in res + def test_annotation_array_index_fancy(): jam = jams.JAMS() @@ -517,6 +409,7 @@ def test_annotation_array_composite(): eq_(len(jam.annotations['beat', 2::2]), 4) + @raises(IndexError) def test_annotation_array_index_error(): @@ -587,6 +480,7 @@ def __test(ext): for ext in ['jams', 'jamz']: yield __test, ext + def test_jams_add(): def __test(): @@ -730,6 +624,10 @@ def __test(filename, fmt): yield raises(jams.ParameterError)(__test), '{:s}.{:s}'.format(badfile, ext), 'auto' yield raises(jams.ParameterError)(__test), '{:s}.{:s}'.format(badfile, ext), ext yield raises(jams.ParameterError)(__test), '{:s}.jams'.format(badfile), ext + + # one last test, trying to load form a non-file-like object + yield raises(jams.ParameterError)(__test), None, 'auto' + os.rmdir(tdir) @@ -824,7 +722,8 @@ def test_annotation_trim_no_duration(): confidence=[None]) expected_ann = jams.Annotation(namespace, data=expected_data, time=5.0, duration=3.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data) + + eq_(ann_trim.data, expected_ann.data) def test_annotation_trim_no_overlap(): @@ -846,7 +745,7 @@ def test_annotation_trim_no_overlap(): assert out[0].category is UserWarning assert 'does not intersect' in str(out[0].message).lower() - assert ann_trim.data.empty + assert len(ann_trim.data) == 0 assert ann_trim.time == ann.time assert ann_trim.duration == 0 @@ -878,7 +777,8 @@ def test_annotation_trim_complete_overlap(): confidence=[0.9, 0.9]) expected_ann = jams.Annotation(namespace, data=expected_data, time=8.0, duration=4.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, check_dtype=False) + + eq_(ann_trim.data, expected_ann.data) # with strict=True ann_trim = ann.trim(8, 12, strict=True) @@ -893,8 +793,8 @@ def test_annotation_trim_complete_overlap(): expected_data = None expected_ann = jams.Annotation(namespace, data=expected_data, time=8.0, duration=4.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, - check_dtype=False, check_index_type=False) + + eq_(ann_trim.data, expected_ann.data) def test_annotation_trim_partial_overlap_beginning(): @@ -923,7 +823,8 @@ def test_annotation_trim_partial_overlap_beginning(): confidence=[0.9, 0.9]) expected_ann = jams.Annotation(namespace, data=expected_data, time=5.0, duration=3.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, check_dtype=False) + + eq_(ann_trim.data, expected_ann.data) # strict=True ann_trim = ann.trim(0, 8, strict=True) @@ -941,7 +842,8 @@ def test_annotation_trim_partial_overlap_beginning(): confidence=[0.9]) expected_ann = jams.Annotation(namespace, data=expected_data, time=5.0, duration=3.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, check_dtype=False) + + eq_(ann_trim.data, expected_ann.data) def test_annotation_trim_partial_overlap_end(): @@ -970,7 +872,8 @@ def test_annotation_trim_partial_overlap_end(): confidence=[0.9, 0.9]) expected_ann = jams.Annotation(namespace, data=expected_data, time=8.0, duration=7.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, check_dtype=False) + + eq_(ann_trim.data, expected_ann.data) # strict=True ann_trim = ann.trim(8, 20, strict=True) @@ -988,7 +891,8 @@ def test_annotation_trim_partial_overlap_end(): confidence=[0.9]) expected_ann = jams.Annotation(namespace, data=expected_data, time=8.0, duration=7.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, check_dtype=False) + + eq_(ann_trim.data, expected_ann.data) def test_annotation_trim_multiple(): @@ -1017,7 +921,8 @@ def test_annotation_trim_multiple(): expected_ann = jams.Annotation(namespace, data=expected_data, time=8.0, duration=2.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, check_dtype=False) + + eq_(ann_trim.data, expected_ann.data) # strict=True ann_trim = ann.trim(0, 10, strict=True).trim(8, 20, strict=True) @@ -1033,8 +938,8 @@ def test_annotation_trim_multiple(): expected_data = None expected_ann = jams.Annotation(namespace, data=expected_data, time=8.0, duration=2.0) - pdt.assert_frame_equal(ann_trim.data, expected_ann.data, - check_dtype=False, check_index_type=False) + + eq_(ann_trim.data, expected_ann.data) def test_jams_trim_no_duration(): @@ -1084,7 +989,7 @@ def test_jams_trim_valid(): jam_trim = jam.trim(0, 10, strict=False) for ann in jam_trim.annotations: - pdt.assert_frame_equal(ann_trim.data, ann.data, check_dtype=False) + eq_(ann.data, ann_trim.data) assert jam_trim.file_metadata.duration == jam.file_metadata.duration assert jam_trim.sandbox.trim == [{'start_time': 0, 'end_time': 10}] @@ -1094,7 +999,7 @@ def test_jams_trim_valid(): ann_trim = ann_copy.trim(0, 10).trim(8, 10) for ann in jam_trim.annotations: - pdt.assert_frame_equal(ann_trim.data, ann.data, check_dtype=False) + eq_(ann.data, ann_trim.data) assert jam_trim.sandbox.trim == ( [{'start_time': 0, 'end_time': 10}, {'start_time': 8, 'end_time': 10}]) @@ -1122,8 +1027,9 @@ def test_annotation_slice(): expected_ann = jams.Annotation(namespace, data=expected_data, time=0, duration=2.0) - pdt.assert_frame_equal(ann_slice.data, expected_ann.data, check_dtype=False) - assert ann_slice.sandbox.slice == ( + + eq_(ann_slice.data, expected_ann.data) + eq_(ann_slice.sandbox.slice, [{'start_time': 8, 'end_time': 10, 'slice_start': 8, 'slice_end': 10}]) # Slice out range that's partially inside the time range spanned by the @@ -1136,7 +1042,8 @@ def test_annotation_slice(): expected_ann = jams.Annotation(namespace, data=expected_data, time=2.0, duration=5.0) - pdt.assert_frame_equal(ann_slice.data, expected_ann.data, check_dtype=False) + + eq_(ann_slice.data, expected_ann.data) assert ann_slice.sandbox.slice == ( [{'start_time': 3, 'end_time': 10, 'slice_start': 5, 'slice_end': 10}]) @@ -1150,7 +1057,8 @@ def test_annotation_slice(): expected_ann = jams.Annotation(namespace, data=expected_data, time=0, duration=2.0) - pdt.assert_frame_equal(ann_slice.data, expected_ann.data, check_dtype=False) + + eq_(ann_slice.data, expected_ann.data) assert ann_slice.sandbox.slice == ( [{'start_time': 8, 'end_time': 20, 'slice_start': 8, 'slice_end': 15}]) @@ -1163,7 +1071,8 @@ def test_annotation_slice(): expected_ann = jams.Annotation(namespace, data=expected_data, time=0, duration=2.0) - pdt.assert_frame_equal(ann_slice.data, expected_ann.data, check_dtype=False) + + eq_(ann_slice.data, expected_ann.data) assert ann_slice.sandbox.slice == ( [{'start_time': 0, 'end_time': 10, 'slice_start': 5, 'slice_end': 10}, {'start_time': 8, 'end_time': 10, 'slice_start': 8, 'slice_end': 10}]) @@ -1202,7 +1111,7 @@ def __test_error(jam, start_time, end_time, strict=False): jam_slice = jam.slice(0, 10, strict=False) for ann in jam_slice.annotations: - pdt.assert_frame_equal(ann_slice.data, ann.data, check_dtype=False) + eq_(ann.data, ann_slice.data) assert jam_slice.file_metadata.duration == 10 assert jam_slice.sandbox.slice == [{'start_time': 0, 'end_time': 10}] @@ -1212,7 +1121,7 @@ def __test_error(jam, start_time, end_time, strict=False): ann_slice = ann_copy.slice(0, 10).slice(8, 10) for ann in jam_slice.annotations: - pdt.assert_frame_equal(ann_slice.data, ann.data, check_dtype=False) + eq_(ann.data, ann_slice.data) assert jam_slice.sandbox.slice == ( [{'start_time': 0, 'end_time': 10}, {'start_time': 8, 'end_time': 10}]) @@ -1224,3 +1133,43 @@ def __test_error(jam, start_time, end_time, strict=False): del slice_metadata['duration'] assert slice_metadata == orig_metadata assert jam_slice.file_metadata.duration == 2 + + +def test_annotation_data_frame(): + namespace = 'tag_open' + data = dict(time=[5.0, 5.0, 10.0], + duration=[2.0, 4.0, 4.0], + value=['one', 'two', 'three'], + confidence=[0.9, 0.9, 0.9]) + ann = jams.Annotation(namespace, data=data, time=5.0, duration=10.0) + + df = ann.to_dataframe() + + eq_(list(df.columns), ['time', 'duration', 'value', 'confidence']) + + for i, row in df.iterrows(): + eq_(row.time, data['time'][i]) + eq_(row.duration, data['duration'][i]) + eq_(row.value, data['value'][i]) + eq_(row.confidence, data['confidence'][i]) + + +def test_deprecated(): + + @jams.core.deprecated('old version', 'new version') + def _foo(): + pass + + warnings.resetwarnings() + warnings.simplefilter('always') + with warnings.catch_warnings(record=True) as out: + _foo() + + # And that the warning triggered + assert len(out) > 0 + + # And that the category is correct + assert out[0].category is DeprecationWarning + + # And that it says the right thing (roughly) + assert 'deprecated' in str(out[0].message).lower() diff --git a/tests/namespace_tests.py b/tests/namespace_tests.py index 4f7513e9..fd85d9b0 100644 --- a/tests/namespace_tests.py +++ b/tests/namespace_tests.py @@ -8,8 +8,7 @@ from nose.tools import raises from jams import SchemaError -from jams import Annotation -import pandas as pd +from jams import Annotation, Observation from util_test import srand @@ -30,12 +29,10 @@ def test_ns_time_invalid(): def __test(data): ann = Annotation(namespace='onset') - # Bypass the safety chceks in add_observation - ann.data.loc[0] = {'time': pd.to_timedelta(data['time'], unit='s'), - 'duration': pd.to_timedelta(data['duration'], - unit='s'), - 'value': None, - 'confdence': None} + # Bypass the safety checks in append + ann.data.add(Observation(time=data['time'], + duration=data['duration'], + value=None, confidence=None)) ann.validate() diff --git a/tests/test_convert.py b/tests/test_convert.py index 2d187997..bc7834a5 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -3,8 +3,6 @@ '''namespace conversion tests''' import numpy as np -import numpy.testing as npt -import pandas.util.testing as pdt from nose.tools import raises, eq_ import jams @@ -41,6 +39,7 @@ def __test(ns): for ns in jams.schema.__NAMESPACE__: yield __test, ns + def test_pitch_hz_to_contour(): ann = jams.Annotation(namespace='pitch_hz') @@ -59,16 +58,17 @@ def test_pitch_hz_to_contour(): eq_(ann2.namespace, 'pitch_contour') # Check index values - eq_(ann2.data.value.iloc[0]['index'], 0) - eq_(ann2.data.value.iloc[-1]['index'], 0) + eq_(ann2.data[0].value['index'], 0) + eq_(ann2.data[-1].value['index'], 0) # Check frequency - eq_(np.abs(ann2.data.value.iloc[0]['frequency']), np.abs(values[0])) - eq_(np.abs(ann2.data.value.iloc[-1]['frequency']), np.abs(values[-1])) + eq_(np.abs(ann2.data[0].value['frequency']), np.abs(values[0])) + eq_(np.abs(ann2.data[-1].value['frequency']), np.abs(values[-1])) # Check voicings - assert not ann2.data.value.iloc[0]['voiced'] - assert ann2.data.value.iloc[-1]['voiced'] + assert not ann2.data[0].value['voiced'] + assert ann2.data[-1].value['voiced'] + def test_pitch_midi_to_contour(): @@ -86,11 +86,11 @@ def test_pitch_midi_to_contour(): eq_(ann2.namespace, 'pitch_contour') # Check index values - eq_(ann2.data.value.iloc[0]['index'], 0) - eq_(ann2.data.value.iloc[-1]['index'], 0) + eq_(ann2.data[0].value['index'], 0) + eq_(ann2.data[-1].value['index'], 0) # Check voicings - assert ann2.data.value.iloc[-1]['voiced'] + assert ann2.data[-1].value['voiced'] def test_pitch_midi_to_hz(): @@ -104,12 +104,15 @@ def test_pitch_midi_to_hz(): # Check the namespace eq_(ann2.namespace, 'pitch_hz') # midi 69 = 440.0 Hz - eq_(ann2.data.value.loc[0], 440.0) + eq_(ann2.data[0].value, 440.0) # Check all else is equal - pdt.assert_series_equal(ann.data.time, ann2.data.time) - pdt.assert_series_equal(ann.data.duration, ann2.data.duration) - pdt.assert_series_equal(ann.data.confidence, ann2.data.confidence) + eq_(len(ann.data), len(ann2.data)) + + for obs1, obs2 in zip(ann.data, ann2.data): + eq_(obs1.time, obs2.time) + eq_(obs1.duration, obs2.duration) + eq_(obs1.confidence, obs2.confidence) def test_pitch_hz_to_midi(): @@ -123,12 +126,15 @@ def test_pitch_hz_to_midi(): # Check the namespace eq_(ann2.namespace, 'pitch_midi') # midi 69 = 440.0 Hz - eq_(ann2.data.value.loc[0], 69) + eq_(ann2.data[0].value, 69) # Check all else is equal - pdt.assert_series_equal(ann.data.time, ann2.data.time) - pdt.assert_series_equal(ann.data.duration, ann2.data.duration) - pdt.assert_series_equal(ann.data.confidence, ann2.data.confidence) + eq_(len(ann.data), len(ann2.data)) + + for obs1, obs2 in zip(ann.data, ann2.data): + eq_(obs1.time, obs2.time) + eq_(obs1.duration, obs2.duration) + eq_(obs1.confidence, obs2.confidence) def test_note_midi_to_hz(): @@ -142,12 +148,15 @@ def test_note_midi_to_hz(): # Check the namespace eq_(ann2.namespace, 'note_hz') # midi 69 = 440.0 Hz - eq_(ann2.data.value.loc[0], 440.0) + eq_(ann2.data[0].value, 440.0) # Check all else is equal - pdt.assert_series_equal(ann.data.time, ann2.data.time) - pdt.assert_series_equal(ann.data.duration, ann2.data.duration) - pdt.assert_series_equal(ann.data.confidence, ann2.data.confidence) + eq_(len(ann.data), len(ann2.data)) + + for obs1, obs2 in zip(ann.data, ann2.data): + eq_(obs1.time, obs2.time) + eq_(obs1.duration, obs2.duration) + eq_(obs1.confidence, obs2.confidence) def test_note_hz_to_midi(): @@ -161,12 +170,16 @@ def test_note_hz_to_midi(): # Check the namespace eq_(ann2.namespace, 'note_midi') # midi 69 = 440.0 Hz - eq_(ann2.data.value.loc[0], 69) + eq_(ann2.data[0].value, 69) # Check all else is equal - pdt.assert_series_equal(ann.data.time, ann2.data.time) - pdt.assert_series_equal(ann.data.duration, ann2.data.duration) - pdt.assert_series_equal(ann.data.confidence, ann2.data.confidence) + eq_(len(ann.data), len(ann2.data)) + + for obs1, obs2 in zip(ann.data, ann2.data): + eq_(obs1.time, obs2.time) + eq_(obs1.duration, obs2.duration) + eq_(obs1.confidence, obs2.confidence) + def test_segment_open(): @@ -181,7 +194,7 @@ def test_segment_open(): eq_(ann2.namespace, 'segment_open') # Check all else is equal - pdt.assert_frame_equal(ann.data, ann2.data) + eq_(ann.data, ann2.data) def test_tag_open(): @@ -197,7 +210,7 @@ def test_tag_open(): eq_(ann2.namespace, 'tag_open') # Check all else is equal - pdt.assert_frame_equal(ann.data, ann2.data) + eq_(ann.data, ann2.data) def test_chord(): @@ -213,7 +226,7 @@ def test_chord(): eq_(ann2.namespace, 'chord', ann2) # Check all else is equal - pdt.assert_frame_equal(ann.data, ann2.data) + assert ann.data == ann2.data def test_beat_position(): @@ -236,12 +249,12 @@ def test_beat_position(): # Check the namespace eq_(ann2.namespace, 'beat') - npt.assert_allclose(ann2.data.value.values, np.arange(1, 5)) - # Check all else is equal - pdt.assert_series_equal(ann.data.time, ann2.data.time) - pdt.assert_series_equal(ann.data.duration, ann2.data.duration) - pdt.assert_series_equal(ann.data.confidence, ann2.data.confidence) + eq_(len(ann), len(ann2)) + for obs1, obs2 in zip(ann.data, ann2.data): + eq_(obs1.time, obs2.time) + eq_(obs1.duration, obs2.duration) + eq_(obs1.confidence, obs2.confidence) def test_can_convert_equal(): diff --git a/tests/util_test.py b/tests/util_test.py index 941af424..9b7b6aaf 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -4,10 +4,9 @@ import tempfile import os -from nose.tools import eq_, raises +from nose.tools import eq_ import numpy as np -import jams from jams import core, util @@ -45,28 +44,18 @@ def __test(ns, lab, ints, y, infer_duration): _, ann = util.import_lab(ns, six.StringIO(lab), infer_duration=infer_duration) - assert np.allclose(core.timedelta_to_float(ann.data['time'].values), - ints[:, 0]) - assert np.allclose(core.timedelta_to_float(ann.data['duration'].values), - ints[:, 1] - ints[:, 0]) - for y1, y2 in zip(list(ann.data['value'].values), y): - eq_(y1, y2) + eq_(len(ints), len(ann.data)) + eq_(len(y), len(ann.data)) + + for yi, ival, obs in zip(y, ints, ann): + eq_(obs.time, ival[0]) + eq_(obs.duration, ival[1] - ival[0]) + eq_(obs.value, yi) for ns, lab, ints, y, inf in zip(namespace, labs, intervals, labels, durations): yield __test, ns, lab, ints, y, inf -def test_timedelta_to_float(): - - # 2.5 seconds - t = 2.5 - x = np.timedelta64(int(t * 1e9)) - tn = core.timedelta_to_float(x) - - # convert back - assert np.allclose(t, tn) - - def test_query_pop(): def __test(query, prefix, sep, target):