From ea8d48d5a3cca64096ef40316cfa4406351f1e0c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 17 Jul 2019 11:42:15 +0300 Subject: [PATCH 01/16] Added annotations dummy data --- cvatjs/tests/mocks/dummy-data.mock.js | 1061 +++++++++++++++++++++++ cvatjs/tests/mocks/server-proxy.mock.js | 10 +- 2 files changed, 1067 insertions(+), 4 deletions(-) diff --git a/cvatjs/tests/mocks/dummy-data.mock.js b/cvatjs/tests/mocks/dummy-data.mock.js index 75498b1d9ae3..9ad4fb8a8a4b 100644 --- a/cvatjs/tests/mocks/dummy-data.mock.js +++ b/cvatjs/tests/mocks/dummy-data.mock.js @@ -881,9 +881,1070 @@ const tasksDummyData = { ] } +const taskAnnotationsDummyData = { + '10': { + "version":21, + "tags":[], + "shapes":[], + "tracks":[ + { + "id": 25, // interpolation + "frame": 10, + "label_id": 19, + "group": 0, + "shapes": [{ + "type": "polygon", + "occluded": false, + "z_order": 2, + "points": [ + 377.64912280702083, + 458.5473684210556, + 383.82456140351314, + 458.5473684210556, + 406.98245614035477, + 455.45964912281124, + 431.6842105263204, + 455.45964912281124, + 457.92982456140817, + 455.45964912281124, + 482.6315789473738, + 455.45964912281124, + 508.87719298246157, + 455.45964912281124, + 535.1228070175493, + 455.45964912281124, + 559.8245614035113, + 455.45964912281124, + 587.6140350877249, + 455.45964912281124, + 620.0350877193014, + 455.45964912281124, + 640.1052631578968, + 455.45964912281124, + 664.8070175438625, + 453.9157894736891, + 692.5964912280724, + 450.8280701754411, + 721.9298245614082, + 450.8280701754411, + 743.5438596491258, + 447.74035087719676, + 769.7894736842136, + 446.1964912280746, + 796.0350877193014, + 446.1964912280746, + 823.8245614035113, + 446.1964912280746, + 846.9824561403548, + 446.1964912280746, + 876.3157894736869, + 446.1964912280746, + 905.6491228070226, + 446.1964912280746, + 931.8947368421104, + 446.1964912280746, + 959.6842105263204, + 446.1964912280746, + 987.4736842105303, + 446.1964912280746, + 1015.2631578947403, + 446.1964912280746, + 1039.964912280706, + 446.1964912280746, + 1066.2105263157937, + 446.1964912280746, + 1090.9122807017593, + 446.1964912280746, + 1115.614035087725, + 446.1964912280746, + 1138.7719298245647, + 449.28421052631893, + 1231.4000000000015, + 413.8000000000011, + 1180.4561403508815, + 467.81052631579223, + 1180.4561403508815, + 494.05614035088, + 1180.4561403508815, + 520.3017543859678, + 1180.4561403508815, + 545.0035087719334, + 1180.4561403508815, + 571.2491228070212, + 1180.4561403508815, + 597.494736842109, + 1180.4561403508815, + 620.6526315789524, + 1180.4561403508815, + 649.9859649122845, + 1180.4561403508815, + 676.2315789473723, + 1180.4561403508815, + 699.3894736842158, + 1180.4561403508815, + 727.1789473684257, + 1180.4561403508815, + 747.2491228070212, + 1180.4561403508815, + 771.9508771929868, + 1180.4561403508815, + 802.8280701754411, + 1180.4561403508815, + 830.6175438596547, + 1180.4561403508815, + 853.7754385964945, + 1180.4561403508815, + 880.0210526315823, + 1183.5438596491258, + 901.6350877193036, + 1183.5438596491258, + 929.4245614035135, + 1186.6315789473738, + 952.5824561403533, + 1188.175438596496, + 975.7403508771968, + 1188.175438596496, + 1001.9859649122845, + 1188.175438596496, + 1023.6000000000022, + 1188.175438596496, + 1057.5649122807044, + 1186.6315789473738, + 1082.26666666667, + 1186.6315789473738, + 1108.5122807017578, + 1186.6315789473738, + 1133.2140350877235, + 1175.82421875, + 1154.828125, + 1155.7543859649159, + 1156.371929824567, + 1132.5964912280724, + 1154.828070175441, + 1106.3508771929846, + 1154.828070175441, + 1078.5614035087747, + 1154.828070175441, + 1053.8596491228127, + 1150.1964912280746, + 1030.7017543859693, + 1148.6526315789524, + 1002.9122807017593, + 1148.6526315789524, + 982.8421052631602, + 1148.6526315789524, + 953.5087719298281, + 1147.1087719298303, + 922.6315789473738, + 1147.1087719298303, + 891.7543859649159, + 1147.1087719298303, + 868.5964912280724, + 1147.1087719298303, + 839.2631578947403, + 1147.1087719298303, + 816.1052631578968, + 1147.1087719298303, + 786.7719298245647, + 1147.1087719298303, + 760.5263157894769, + 1147.1087719298303, + 735.8245614035113, + 1147.1087719298303, + 708.0350877193014, + 1142.47719298246, + 684.8771929824616, + 1140.933333333338, + 658.6315789473738, + 1140.933333333338, + 633.9298245614082, + 1140.933333333338, + 607.6842105263204, + 1139.3894736842158, + 581.4385964912326, + 1134.7578947368456, + 559.8245614035113, + 1133.2140350877235, + 535.1228070175493, + 1131.6701754386013, + 505.7894736842136, + 1131.6701754386013, + 482.6315789473738, + 1131.6701754386013, + 454.8421052631602, + 1130.1263157894791, + 430.1403508771964, + 1130.1263157894791, + 405.4385964912326, + 1130.1263157894791, + 383.82421875, + 1130.126953125, + 382.28070175438916, + 1113.143859649128, + 380.736842105267, + 1088.4421052631624, + 380.736842105267, + 1056.0210526315823, + 380.736842105267, + 1026.6877192982502, + 379.1929824561448, + 1005.0736842105289, + 374.5614035087765, + 978.8280701754411, + 371.47368421053034, + 949.494736842109, + 371.47368421053034, + 921.705263157899, + 371.47368421053034, + 897.0035087719334, + 371.47368421053034, + 866.1263157894791, + 371.47368421053034, + 842.9684210526357, + 371.47368421053034, + 810.5473684210556, + 371.47368421053034, + 778.1263157894791, + 377.64912280702083, + 751.8807017543913, + 380.736842105267, + 722.5473684210556, + 385.3684210526353, + 693.2140350877235, + 385.3684210526353, + 668.5122807017578, + 386.9122807017575, + 643.8105263157922, + 388.45614035088147, + 619.1087719298266, + 388.45614035088147, + 591.3192982456167, + 388.45614035088147, + 563.5298245614067, + 388.45614035088147, + 535.7403508771968, + 388.45614035088147, + 511.03859649123115, + 386.9122807017575, + 487.88070175439134 + ], + "id":382, + "frame":10, + "outside":false, + "attributes": [{ + "spec_id":1, + "value":"__undefined__" + }, { + "spec_id":5, + "value":"non, initialized" + }] + }, { + "type": "polygon", + "occluded": false, + "z_order": 2, + "points": [ + 502.701171875, + 1093.07421875, + 860.8771929824616, + 443.10877192982844, + 1462.9824561403548, + 1120.8631578947425 + ], + "id": 383, + "frame": 20, + "outside": false, + "attributes": [] + }, { + "type": "polygon", + "occluded": false, + "z_order": 2, + "points": [ + 502.701171875, + 1093.07421875, + 860.8771929824616, + 443.10877192982844, + 1462.9824561403548, + 1120.8631578947425 + ], + "id": 384, + "frame": 22, + "outside": true, + "attributes": [] + }], + "attributes": [{ + "spec_id": 2, + "value": "1" + }, { + "spec_id": 3, + "value": "male" + }, { + "spec_id": 4, + "value": "false" + }] + }, + { + "id": 60, + "frame": 0, + "label_id": 19, + "group": 0, + "shapes": [{ + "type": "rectangle", + "occluded": false, + "z_order": 1, + "points": [ + 425.58984375, + 540.298828125, + 755.9765625, + 745.6328125 + ], + "id": 379, + "frame": 0, + "outside": false, + "attributes": [ + { + "spec_id":5, + "value":"non, initialized" + }, + { + "spec_id":1, + "value":"__undefined__" + } + ] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 1, + "points": [ + 238.8000000000011, + 498.6000000000022, + 546.01171875, + 660.720703125 + ], + "id": 380, + "frame": 10, + "outside": false, + "attributes": [] + }, { + "type":"rectangle", + "occluded":false, + "z_order":1, + "points":[ + 13.3955078125, + 447.650390625, + 320.6072265624989, + 609.7710937499978 + ], + "id":381, + "frame":20, + "outside":false, + "attributes":[ + + ] + }], + "attributes":[ + { + "spec_id":2, + "value":"1" + }, + { + "spec_id":3, + "value":"male" + }, + { + "spec_id":4, + "value":"false" + }] + } + ] + }, + '1': { + "version": 16, + "tags": [], + "shapes": [{ + "type": "rectangle", + "occluded": false, + "z_order": 1, + "points": [ + 387.91, + 403.81, + 595.14, + 712.25 + ], + "id": 108, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 2, + "points": [ + 783.12, + 368.91, + 990.35, + 677.34 + ], + "id": 109, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 3, + "points": [ + 1277.1, + 239.99, + 1484.33, + 548.43 + ], + "id": 110, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 4, + "points": [ + 1420.48, + 713.49, + 1627.71, + 1021.92 + ], + "id": 111, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes":[ + + ] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 5, + "points": [ + 896.38, + 659.27, + 1103.61, + 967.71 + ], + "id": 112, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "polygon", + "occluded": false, + "z_order": 6, + "points": [ + 449.36, + 892.97, + 449.36, + 892.97, + 468.63, + 913.46, + 495.14, + 933.94, + 527.67, + 955.62, + 562.61, + 973.7, + 589.12, + 983.34, + 613.21, + 988.15, + 632.49, + 991.77, + 656.59, + 994.18, + 686.71, + 994.18, + 733.69, + 980.93, + 772.25, + 959.24, + 809.6, + 927.91, + 837.31, + 896.59, + 851.77, + 867.67, + 861.41, + 841.17, + 862.61, + 805.02, + 840.92, + 759.24, + 802.37, + 720.68, + 777.07, + 703.82, + 750.56, + 690.56, + 726.47, + 684.54, + 698.75, + 680.92, + 681.89, + 680.92, + 656.59, + 680.92, + 633.69, + 683.33, + 608.39, + 690.56, + 578.27, + 706.22, + 548.15, + 718.27, + 518.03, + 730.32, + 486.71, + 743.57, + 458.99, + 756.83, + 434.9, + 766.47, + 408.39, + 777.31, + 381.89, + 786.95, + 354.17, + 794.18, + 331.28, + 800.2, + 295.14, + 803.82, + 283.09, + 800.2, + 267.43, + 783.33, + 255.38, + 766.47, + 232.49, + 733.94, + 220.44, + 713.45, + 212.0, + 688.15, + 208.39, + 666.47, + 210.8, + 647.19 + ], + "id": 113, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes":[] + }, { + "type": "polygon", + "occluded": false, + "z_order": 7, + "points": [ + 1260.84, + 344.81, + 1260.84, + 344.81, + 1280.11, + 365.29, + 1306.62, + 385.78, + 1339.15, + 407.46, + 1374.09, + 425.53, + 1400.6, + 435.17, + 1424.69, + 439.99, + 1443.97, + 443.61, + 1468.07, + 446.02, + 1498.19, + 446.02, + 1545.18, + 432.76, + 1583.73, + 411.08, + 1621.08, + 379.75, + 1648.79, + 348.43, + 1663.25, + 319.51, + 1672.89, + 293.0, + 1674.09, + 256.86, + 1652.41, + 211.08, + 1613.85, + 172.52, + 1588.55, + 155.65, + 1562.04, + 142.4, + 1537.95, + 136.38, + 1510.24, + 132.76, + 1493.37, + 132.76, + 1468.07, + 132.76, + 1445.18, + 135.17, + 1419.87, + 142.4, + 1389.75, + 158.06, + 1359.63, + 170.11, + 1329.51, + 182.16, + 1298.19, + 195.41, + 1270.48, + 208.67, + 1246.38, + 218.3, + 1219.87, + 229.15, + 1193.37, + 238.79, + 1165.66, + 246.02, + 1142.76, + 252.04, + 1106.62, + 255.65, + 1094.57, + 252.04, + 1078.91, + 235.17, + 1066.86, + 218.3, + 1043.97, + 185.77, + 1031.92, + 165.29, + 1023.49, + 139.99, + 1019.87, + 118.3, + 1022.28, + 99.03 + ], + "id": 114, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "polygon", + "occluded": false, + "z_order": 8, + "points": [ + 1113.21, + 723.09, + 1322.86, + 1018.28, + 1562.62, + 873.7, + 1587.92, + 641.16, + 1267.43, + 530.32 + ], + "id": 115, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "polyline", + "occluded": false, + "z_order": 9, + "points": [ + 268.63, + 359.23, + 277.07, + 344.78, + 292.73, + 325.5, + 312.01, + 311.04, + 331.28, + 300.2, + 349.36, + 295.38, + 375.86, + 290.56, + 387.91, + 290.56, + 418.03, + 290.56, + 439.72, + 292.97, + 457.79, + 295.38, + 492.73, + 301.4, + 525.26, + 306.22, + 534.9, + 306.22, + 571.04, + 296.58, + 591.53, + 284.54, + 610.8, + 272.49, + 640.92, + 253.21, + 655.38, + 238.75 + ], + "id": 116, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "points", + "occluded": false, + "z_order": 10, + "points": [ + 1089.12, + 505.02, + 1178.28, + 543.57, + 1074.66, + 602.61, + 1109.6, + 680.92, + 1172.25, + 631.53, + 1036.11, + 576.1, + 1057.79, + 445.98, + 1185.51, + 400.2 + ], + "id": 117, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [ + + ] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1565.03, + 555.62, + 1787.92, + 765.26 + ], + "id": 118, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 100.0, + 100.0 + ], + "id": 119, + "frame": 1, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 0.0, + 100.0, + 200.0 + ], + "id": 120, + "frame": 1, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 121, + "frame": 1, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 122, + "frame": 1, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 123, + "frame": 2, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 124, + "frame": 2, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 125, + "frame": 3, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 126, + "frame": 3, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 127, + "frame": 4, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 128, + "frame": 4, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 129, + "frame": 5, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 130, + "frame": 5, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 131, + "frame": 6, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 132, + "frame": 6, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 133, + "frame": 7, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 134, + "frame": 7, + "label_id": 2, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": false, + "z_order": 11, + "points": [ + 1211.6, + 500.48, + 1434.49, + 710.12 + ], + "id": 135, + "frame": 8, + "label_id": 1, + "group": 0, + "attributes": [] + }, { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [ + 0.0, + 0.0, + 200.0, + 200.0 + ], + "id": 136, + "frame": 8, + "label_id": 2, + "group": 0, + "attributes": [] + }], + "tracks":[] + } +}; + +const jobAnnotationsDummyData = JSON.parse(JSON.stringify(taskAnnotationsDummyData)); + module.exports = { tasksDummyData, aboutDummyData, shareDummyData, usersDummyData, + taskAnnotationsDummyData, + jobAnnotationsDummyData, } \ No newline at end of file diff --git a/cvatjs/tests/mocks/server-proxy.mock.js b/cvatjs/tests/mocks/server-proxy.mock.js index 5b5f0e5e8f10..b4ad4e5d3679 100644 --- a/cvatjs/tests/mocks/server-proxy.mock.js +++ b/cvatjs/tests/mocks/server-proxy.mock.js @@ -14,6 +14,8 @@ const { aboutDummyData, shareDummyData, usersDummyData, + taskAnnotationsDummyData, + jobAnnotationsDummyData, } = require('./dummy-data.mock'); @@ -185,19 +187,19 @@ class ServerProxy { return JSON.parse(JSON.stringify(usersDummyData)).results[0]; } - async function getFrame() { + async function getFrame(tid, frame) { return null; } - async function getMeta() { + async function getMeta(tid) { return null; } - async function getAnnotations() { + async function getAnnotations(session, id) { return null; } - async function updateAnnotations() { + async function updateAnnotations(session, id, data, action) { return null; } From 8bc4b92a8a817f8825c85ef9450318a54d35aeeb Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 17 Jul 2019 12:43:50 +0300 Subject: [PATCH 02/16] Added test file --- cvatjs/tests/api/annotations.js | 143 ++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 cvatjs/tests/api/annotations.js diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js new file mode 100644 index 000000000000..d04a01a367f1 --- /dev/null +++ b/cvatjs/tests/api/annotations.js @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2018 Intel Corporation + * SPDX-License-Identifier: MIT +*/ + +/* global + require:false + jest:false + describe:false +*/ + +// Setup mock for a server +jest.mock('../../src/server-proxy', () => { + const mock = require('../mocks/server-proxy.mock'); + return mock; +}); + +// Initialize api +require('../../src/api'); + +// Test cases +describe('Feature: get annotations', () => { + test('get annotations from a task', async () => { + // TODO: + }); + + test('get annotations from a job', async () => { + // TODO: + }); + + // TODO: Test filter +}); + + +describe('Feature: put annotations', () => { + test('put annotations from a task', async () => { + + }); + + test('put annotations from a job', async () => { + + }); + + // TODO: Put with invalid arguments (2-3 tests) +}); + +describe('Feature: check unsaved changes', () => { + test('check unsaved changes in a task', async () => { + + }); + + test('check unsaved changes in a job', async () => { + + }); +}); + +describe('Feature: save annotations', () => { + test('save annotations from a task', async () => { + + }); + + test('save annotations from a job', async () => { + + }); +}); + +describe('Feature: merge annotations', () => { + test('merge annotations in a task', async () => { + + }); + + test('merge annotations in a job', async () => { + + }); + + // TODO: merge with invalid parameters +}); + +describe('Feature: split annotations', () => { + test('split annotations in a task', async () => { + + }); + + test('split annotations in a job', async () => { + + }); + + // TODO: split with invalid parameters +}); + +describe('Feature: group annotations', () => { + test('group annotations in a task', async () => { + + }); + + test('group annotations in a job', async () => { + + }); + + // TODO: group with invalid parameters +}); + +describe('Feature: clear annotations', () => { + test('clear annotations in a task', async () => { + + }); + + test('clear annotations in a job', async () => { + + }); + + test('clear annotations with reload in a task', async () => { + + }); + + test('clear annotations with reload in a job', async () => { + + }); + + // TODO: clear with invalid parameter +}); + +describe('Feature: get statistics', () => { + test('get statistics from a task', async () => { + + }); + + test('get statistics from a job', async () => { + + }); +}); + +describe('Feature: select object', () => { + test('select object in a task', async () => { + + }); + + test('select object in a job', async () => { + + }); + + // TODO: select with invalid parameters +}); From 75b6e59887e0569cf8e060a0608c524ef6d6decc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 17 Jul 2019 17:01:38 +0300 Subject: [PATCH 03/16] Weak maps instead of regular dict --- cvatjs/src/annotations-objects.js | 4 +-- cvatjs/src/annotations.js | 50 +++++++++++++++---------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/cvatjs/src/annotations-objects.js b/cvatjs/src/annotations-objects.js index 7057c1b55224..515bcc9cee29 100644 --- a/cvatjs/src/annotations-objects.js +++ b/cvatjs/src/annotations-objects.js @@ -519,7 +519,7 @@ // Remove all cache after this keyframe because it have just become outdated for (const cacheFrame in this.cache) { if (+cacheFrame > frame) { - delete this.cache[frame]; + delete this.cache[cacheFrame]; } } @@ -535,7 +535,7 @@ // Remove all cache after this keyframe because it have just become outdated for (const cacheFrame in this.cache) { if (+cacheFrame > frame) { - delete this.cache[frame]; + delete this.cache[cacheFrame]; } } diff --git a/cvatjs/src/annotations.js b/cvatjs/src/annotations.js index 805d1c1ac222..67683d4ff417 100644 --- a/cvatjs/src/annotations.js +++ b/cvatjs/src/annotations.js @@ -12,8 +12,8 @@ const Collection = require('./annotations-collection'); const AnnotationsSaver = require('./annotations-saver'); - const jobCache = {}; - const taskCache = {}; + const jobCache = new WeakMap(); + const taskCache = new WeakMap(); function getCache(sessionType) { if (sessionType === 'task') { @@ -33,17 +33,17 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (!(session.id in cache)) { + if (!cache.has(session)) { const rawAnnotations = await serverProxy.annotations .getAnnotations(sessionType, session.id); const collection = new Collection(session.labels || session.task.labels) .import(rawAnnotations); const saver = new AnnotationsSaver(rawAnnotations.version, collection, session); - cache[session.id] = { + cache.set(session, { collection, saver, - }; + }); } } @@ -51,15 +51,15 @@ await getAnnotationsFromServer(session); const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - return cache[session.id].collection.get(frame, filter); + return cache.get(session).collection.get(frame, filter); } async function saveAnnotations(session, onUpdate) { const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - await cache[session.id].saver.save(onUpdate); + if (cache.has(session)) { + await cache.get(session).saver.save(onUpdate); } // If a collection wasn't uploaded, than it wasn't changed, finally we shouldn't save it @@ -69,8 +69,8 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - return cache[session.id].collection.merge(objectStates); + if (cache.has(session)) { + return cache.get(session).collection.merge(objectStates); } throw window.cvat.exceptions.DataError( @@ -82,8 +82,8 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - return cache[session.id].collection.split(objectState, frame); + if (cache.has(session)) { + return cache.get(session).collection.split(objectState, frame); } throw window.cvat.exceptions.DataError( @@ -95,8 +95,8 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - return cache[session.id].collection.group(objectStates, reset); + if (cache.has(session)) { + return cache.get(session).collection.group(objectStates, reset); } throw window.cvat.exceptions.DataError( @@ -108,8 +108,8 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - return cache[session.id].saver.hasUnsavedChanges(); + if (cache.has(session)) { + return cache.get(session).saver.hasUnsavedChanges(); } return false; @@ -119,12 +119,12 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - cache[session.id].collection.clear(); + if (cache.has(session)) { + cache.get(session).collection.clear(); } if (reload) { - delete cache[session.id]; + cache.delete(session); await getAnnotationsFromServer(session); } } @@ -133,8 +133,8 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - return cache[session.id].collection.statistics(); + if (cache.has(session)) { + return cache.get(session).collection.statistics(); } throw window.cvat.exceptions.DataError( @@ -146,8 +146,8 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - return cache[session.id].collection.put(objectStates); + if (cache.has(session)) { + return cache.get(session).collection.put(objectStates); } throw window.cvat.exceptions.DataError( @@ -159,8 +159,8 @@ const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); - if (session.id in cache) { - return cache[session.id].collection.select(objectStates, x, y); + if (cache.has(session)) { + return cache.get(session).collection.select(objectStates, x, y); } throw window.cvat.exceptions.DataError( From 290c3ac9bd6abd55e224f94fc540e7e7d80d6f7c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 17 Jul 2019 17:09:26 +0300 Subject: [PATCH 04/16] Added 14 API tests --- cvatjs/tests/api/annotations.js | 214 +++++++++++- cvatjs/tests/api/tasks.js | 4 +- cvatjs/tests/mocks/dummy-data.mock.js | 432 +++++++++++++++++++++++- cvatjs/tests/mocks/server-proxy.mock.js | 31 ++ 4 files changed, 664 insertions(+), 17 deletions(-) diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index d04a01a367f1..61697e5626f6 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -21,24 +21,103 @@ require('../../src/api'); // Test cases describe('Feature: get annotations', () => { test('get annotations from a task', async () => { - // TODO: + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + expect(Array.isArray(annotations)).toBeTruthy(); + expect(annotations).toHaveLength(11); + for (const state of annotations) { + expect(state).toBeInstanceOf(window.cvat.classes.ObjectState); + } }); test('get annotations from a job', async () => { - // TODO: + const job = (await window.cvat.jobs.get({ jobID: 101 }))[0]; + const annotations0 = await job.annotations.get(0); + const annotations10 = await job.annotations.get(10); + expect(Array.isArray(annotations0)).toBeTruthy(); + expect(Array.isArray(annotations10)).toBeTruthy(); + expect(annotations0).toHaveLength(1); + expect(annotations10).toHaveLength(2); + for (const state of annotations0.concat(annotations10)) { + expect(state).toBeInstanceOf(window.cvat.classes.ObjectState); + } }); - // TODO: Test filter -}); + test('get annotations for frame out of task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + // Out of task + await expect(task.annotations.get(500)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); -describe('Feature: put annotations', () => { - test('put annotations from a task', async () => { + // Out of task + await expect(task.annotations.get(-1)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + test('get annotations for frame out of job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 101 }))[0]; + + // Out of segment + await expect(job.annotations.get(500)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + // Out of segment + await expect(job.annotations.get(-1)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); - test('put annotations from a job', async () => { + // TODO: Test filter (hasn't been implemented yet) +}); + + +describe('Feature: put annotations', () => { + test('put annotations to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + let annotations = await task.annotations.get(1); + expect(Array.isArray(annotations)).toBeTruthy(); + expect(annotations).toHaveLength(0); + + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + attributes: {}, + points: [0, 0, 100, 0, 100, 50], + occuded: true, + label: task.labels[0], + group: 0, + zOrder: 0, + }); + + await task.annotations.put([state]); + annotations = await task.annotations.get(1); + expect(Array.isArray(annotations)).toBeTruthy(); + expect(annotations).toHaveLength(1); + }); + test('put annotations to a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + let annotations = await job.annotations.get(5); + expect(Array.isArray(annotations)).toBeTruthy(); + expect(annotations).toHaveLength(2); + + const state = new window.cvat.classes.ObjectState({ + frame: 5, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + attributes: {}, + points: [0, 0, 100, 100], + occuded: false, + label: job.task.labels[0], + group: 0, + zOrder: 0, + }); + + await job.annotations.put([state]); + annotations = await job.annotations.get(5); + expect(Array.isArray(annotations)).toBeTruthy(); + expect(annotations).toHaveLength(3); }); // TODO: Put with invalid arguments (2-3 tests) @@ -46,21 +125,123 @@ describe('Feature: put annotations', () => { describe('Feature: check unsaved changes', () => { test('check unsaved changes in a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + expect(await task.annotations.hasUnsavedChanges()).toBe(false); + const annotations = await task.annotations.get(0); + + annotations[0].keyframe = true; + await annotations[0].save(); + expect(await task.annotations.hasUnsavedChanges()).toBe(true); }); test('check unsaved changes in a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + expect(await job.annotations.hasUnsavedChanges()).toBe(false); + const annotations = await job.annotations.get(0); + + annotations[0].occluded = true; + await annotations[0].save(); + expect(await job.annotations.hasUnsavedChanges()).toBe(true); }); }); describe('Feature: save annotations', () => { - test('save annotations from a task', async () => { + test('create & save annotations for a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + let annotations = await task.annotations.get(0); + const { length } = annotations; + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + attributes: {}, + points: [0, 0, 100, 0, 100, 50], + occuded: true, + label: task.labels[0], + group: 0, + zOrder: 0, + }); + + expect(await task.annotations.hasUnsavedChanges()).toBe(false); + await task.annotations.put(state); + expect(await task.annotations.hasUnsavedChanges()).toBe(true); + await task.annotations.save(); + expect(await task.annotations.hasUnsavedChanges()).toBe(false); + annotations = await task.annotations.get(0); + expect(annotations).toHaveLength(length + 1); + }); + + test('update & save annotations for a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + + expect(await task.annotations.hasUnsavedChanges()).toBe(false); + annotations[0].occluded = true; + await annotations[0].save(); + expect(await task.annotations.hasUnsavedChanges()).toBe(true); + await task.annotations.save(); + expect(await task.annotations.hasUnsavedChanges()).toBe(false); + }); + + test('delete & save annotations for a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + + expect(await task.annotations.hasUnsavedChanges()).toBe(false); + await annotations[0].delete(); + expect(await task.annotations.hasUnsavedChanges()).toBe(true); + await task.annotations.save(); + expect(await task.annotations.hasUnsavedChanges()).toBe(false); + }); + + test('create & save annotations for a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + let annotations = await job.annotations.get(0); + const { length } = annotations; + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + attributes: {}, + points: [0, 0, 100, 0, 100, 50], + occuded: true, + label: job.task.labels[0], + group: 0, + zOrder: 0, + }); + + expect(await job.annotations.hasUnsavedChanges()).toBe(false); + await job.annotations.put(state); + expect(await job.annotations.hasUnsavedChanges()).toBe(true); + await job.annotations.save(); + expect(await job.annotations.hasUnsavedChanges()).toBe(false); + annotations = await job.annotations.get(0); + expect(annotations).toHaveLength(length + 1); + }); + test('update & save annotations for a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const annotations = await job.annotations.get(0); + + expect(await job.annotations.hasUnsavedChanges()).toBe(false); + annotations[0].points = [0, 100, 200, 300]; + await annotations[0].save(); + expect(await job.annotations.hasUnsavedChanges()).toBe(true); + await job.annotations.save(); + expect(await job.annotations.hasUnsavedChanges()).toBe(false); }); - test('save annotations from a job', async () => { + test('delete & save annotations for a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const annotations = await job.annotations.get(0); + expect(await job.annotations.hasUnsavedChanges()).toBe(false); + await annotations[0].delete(); + expect(await job.annotations.hasUnsavedChanges()).toBe(true); + await job.annotations.save(); + expect(await job.annotations.hasUnsavedChanges()).toBe(false); }); }); @@ -74,6 +255,8 @@ describe('Feature: merge annotations', () => { }); // TODO: merge with invalid parameters + // Not object states + // Created object state }); describe('Feature: split annotations', () => { @@ -85,7 +268,7 @@ describe('Feature: split annotations', () => { }); - // TODO: split with invalid parameters + // TODO: split with invalid parameters (invalid frame, frame outside of shape etc.) }); describe('Feature: group annotations', () => { @@ -97,7 +280,7 @@ describe('Feature: group annotations', () => { }); - // TODO: group with invalid parameters + // TODO: group with invalid parameters (some values are invalid) }); describe('Feature: clear annotations', () => { @@ -117,7 +300,7 @@ describe('Feature: clear annotations', () => { }); - // TODO: clear with invalid parameter + // TODO: clear with invalid parameter (not a boolean) }); describe('Feature: get statistics', () => { @@ -140,4 +323,11 @@ describe('Feature: select object', () => { }); // TODO: select with invalid parameters + // frame outside of range + // frame is not a number + // invalid coordinates (not number) }); + + +// TODO: Tests for object state +// TODO: Tests for frames diff --git a/cvatjs/tests/api/tasks.js b/cvatjs/tests/api/tasks.js index 3f317edb38b7..c1b1e7210216 100644 --- a/cvatjs/tests/api/tasks.js +++ b/cvatjs/tests/api/tasks.js @@ -26,7 +26,7 @@ describe('Feature: get a list of tasks', () => { test('get all tasks', async () => { const result = await window.cvat.tasks.get(); expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(3); + expect(result).toHaveLength(5); for (const el of result) { expect(el).toBeInstanceOf(Task); } @@ -61,7 +61,7 @@ describe('Feature: get a list of tasks', () => { mode: 'interpolation', }); expect(Array.isArray(result)).toBeTruthy(); - expect(result).toHaveLength(2); + expect(result).toHaveLength(3); for (const el of result) { expect(el).toBeInstanceOf(Task); expect(el.mode).toBe('interpolation'); diff --git a/cvatjs/tests/mocks/dummy-data.mock.js b/cvatjs/tests/mocks/dummy-data.mock.js index 9ad4fb8a8a4b..349290fda037 100644 --- a/cvatjs/tests/mocks/dummy-data.mock.js +++ b/cvatjs/tests/mocks/dummy-data.mock.js @@ -106,10 +106,436 @@ const shareDummyData = [ ] const tasksDummyData = { - "count": 3, + "count": 4, "next": null, "previous": null, "results": [ + { + "url": "http://localhost:7000/api/v1/tasks/1", + "id": 100, + "name": "Image Task", + "size": 9, + "mode": "annotation", + "owner": 1, + "assignee": null, + "bug_tracker": "", + "created_date": "2019-06-18T13:05:08.941304+03:00", + "updated_date": "2019-07-16T15:51:29.142871+03:00", + "overlap": 0, + "segment_size": 0, + "z_order": false, + "status": "annotation", + "labels": [ + { + "id": 1, + "name": "car,", + "attributes": [ + + ] + }, + { + "id": 2, + "name": "person", + "attributes": [ + + ] + } + ], + "segments": [ + { + "start_frame": 0, + "stop_frame": 8, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/1", + "id": 100, + "assignee": null, + "status": "annotation" + } + ] + } + ], + "image_quality": 50, + "start_frame": 0, + "stop_frame": 0, + "frame_filter": "" + }, + { + "url": "http://localhost:7000/api/v1/tasks/10", + "id": 101, + "name": "Video Task", + "size": 5002, + "mode": "interpolation", + "owner": 1, + "assignee": null, + "bug_tracker": "", + "created_date": "2019-06-21T16:34:49.199691+03:00", + "updated_date": "2019-07-12T16:43:58.904892+03:00", + "overlap": 5, + "segment_size": 500, + "z_order": false, + "status": "annotation", + "labels": [ + { + "id": 22, + "name": "bicycle", + "attributes":[ + { + "id": 13, + "name": "driver", + "mutable": false, + "input_type": "radio", + "default_value": "man", + "values": [ + "man", + "woman" + ] + }, + { + "id": 14, + "name": "sport", + "mutable": true, + "input_type": "checkbox", + "default_value": "false", + "values": [ + "false" + ] + } + ] + }, + { + "id": 21, + "name": "car", + "attributes": [ + { + "id": 10, + "name": "model", + "mutable": false, + "input_type": "select", + "default_value": "__undefined__", + "values": [ + "__undefined__", + "bmw", + "mazda", + "suzuki", + "kia" + ] + }, + { + "id": 11, + "name": "driver", + "mutable": false, + "input_type": "select", + "default_value": "__undefined__", + "values": [ + "__undefined__", + "man", + "woman" + ] + }, + { + "id": 12, + "name": "parked", + "mutable": true, + "input_type": "checkbox", + "default_value": "true", + "values": [ + "true" + ] + } + ] + }, + { + "id": 20, + "name": "face", + "attributes": [ + { + "id": 6, + "name": "age", + "mutable": false, + "input_type": "select", + "default_value": "__undefined__", + "values": [ + "__undefined__", + "skip", + "baby (0-5)", + "child (6-12)", + "adolescent (13-19)", + "adult (20-45)", + "middle-age (46-64)", + "old (65-)" + ] + }, + { + "id": 7, + "name": "glass", + "mutable": false, + "input_type": "select", + "default_value": "__undefined__", + "values": [ + "__undefined__", + "skip", + "no", + "sunglass", + "transparent", + "other" + ] + }, + { + "id": 8, + "name": "beard", + "mutable": false, + "input_type": "select", + "default_value": "__undefined__", + "values": [ + "__undefined__", + "skip", + "no", + "yes" + ] + }, + { + "id": 9, + "name": "race", + "mutable": false, + "input_type": "select", + "default_value": "__undefined__", + "values": [ + "__undefined__", + "skip", + "asian", + "black", + "caucasian", + "other" + ] + } + ] + }, + { + "id": 23, + "name": "motorcycle", + "attributes": [ + { + "id": 15, + "name": "model", + "mutable": false, + "input_type": "text", + "default_value": "unknown", + "values": [ + "unknown" + ] + } + ] + }, + { + "id": 19, + "name": "person, pedestrian", + "attributes": [ + { + "id": 1, + "name": "action", + "mutable": true, + "input_type": "select", + "default_value": "__undefined__", + "values": [ + "__undefined__", + "sitting", + "raising_hand", + "standing" + ] + }, + { + "id": 2, + "name": "age", + "mutable": false, + "input_type": "number", + "default_value": "1", + "values": [ + "1", + "100", + "1" + ] + }, + { + "id": 3, + "name": "gender", + "mutable" :false, + "input_type": "select", + "default_value": "male", + "values": [ + "male", + "female" + ] + }, + { + "id": 4, + "name": "false positive", + "mutable": false, + "input_type": "checkbox", + "default_value": "false", + "values": [ + "false" + ] + }, + { + "id": 5, + "name": "clother", + "mutable": true, + "input_type": "text", + "default_value": "non, initialized", + "values": [ + "non, initialized" + ] + } + ] + }, + { + "id": 24, + "name": "road", + "attributes": [ + + ] + } + ], + "segments": [ + { + "start_frame": 0, + "stop_frame": 499, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/10", + "id": 101, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 495, + "stop_frame": 994, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/11", + "id": 102, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 990, + "stop_frame": 1489, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/12", + "id": 103, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 1485, + "stop_frame": 1984, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/13", + "id": 104, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 1980, + "stop_frame": 2479, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/14", + "id": 105, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 2475, + "stop_frame": 2974, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/15", + "id": 106, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 2970, + "stop_frame": 3469, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/16", + "id": 107, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 3465, + "stop_frame": 3964, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/17", + "id": 108, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 3960, + "stop_frame": 4459, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/18", + "id": 109, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 4455, + "stop_frame": 4954, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/19", + "id": 110, + "assignee": null, + "status": "annotation" + } + ] + }, + { + "start_frame": 4950, + "stop_frame": 5001, + "jobs": [ + { + "url": "http://localhost:7000/api/v1/jobs/20", + "id": 111, + "assignee": null, + "status": "annotation" + } + ] + } + ], + "image_quality": 50, + "start_frame": 0, + "stop_frame": 5001, + "frame_filter": "" + }, { "url": "http://localhost:7000/api/v1/tasks/3", "id": 3, @@ -882,7 +1308,7 @@ const tasksDummyData = { } const taskAnnotationsDummyData = { - '10': { + '101': { "version":21, "tags":[], "shapes":[], @@ -1257,7 +1683,7 @@ const taskAnnotationsDummyData = { } ] }, - '1': { + '100': { "version": 16, "tags": [], "shapes": [{ diff --git a/cvatjs/tests/mocks/server-proxy.mock.js b/cvatjs/tests/mocks/server-proxy.mock.js index b4ad4e5d3679..cbc6ae55042c 100644 --- a/cvatjs/tests/mocks/server-proxy.mock.js +++ b/cvatjs/tests/mocks/server-proxy.mock.js @@ -196,10 +196,41 @@ class ServerProxy { } async function getAnnotations(session, id) { + if (session === 'task') { + return JSON.parse(JSON.stringify(taskAnnotationsDummyData[id])); + } + + if (session === 'job') { + return JSON.parse(JSON.stringify(jobAnnotationsDummyData[id])); + } + return null; } async function updateAnnotations(session, id, data, action) { + // Actually we do not change our dummy data + // We just update the argument in some way and return it + + data.version += 1; + + if (action === 'create') { + let idGenerator = 1000; + data.tracks.concat(data.tags).concat(data.shapes).map((el) => { + el.id = ++idGenerator; + return el; + }); + + return data; + } + + if (action === 'update') { + return data; + } + + if (action === 'delete') { + return data; + } + return null; } From e95fff805b2a3a9ee7564143e0cb766f4fe88a1f Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 10:06:08 +0300 Subject: [PATCH 05/16] Fixed put tests --- cvatjs/src/object-state.js | 6 +++++- cvatjs/tests/api/annotations.js | 26 ++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/cvatjs/src/object-state.js b/cvatjs/src/object-state.js index 75672694b2a4..669b9c7ce48e 100644 --- a/cvatjs/src/object-state.js +++ b/cvatjs/src/object-state.js @@ -277,11 +277,15 @@ this.outside = serialized.outside; this.keyframe = serialized.keyframe; this.occluded = serialized.occluded; - this.attributes = serialized.attributes; this.points = serialized.points; this.color = serialized.color; this.lock = serialized.lock; + // It can be undefined in a constructor and it can be defined later + if (typeof (serialized.attributes) !== 'undefined') { + this.attributes = serialized.attributes; + } + data.updateFlags.reset(); } diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index 61697e5626f6..a4ae108d843f 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -75,8 +75,7 @@ describe('Feature: put annotations', () => { test('put annotations to a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; let annotations = await task.annotations.get(1); - expect(Array.isArray(annotations)).toBeTruthy(); - expect(annotations).toHaveLength(0); + const { length } = annotations; const state = new window.cvat.classes.ObjectState({ frame: 1, @@ -84,7 +83,7 @@ describe('Feature: put annotations', () => { shapeType: window.cvat.enums.ObjectShape.POLYGON, attributes: {}, points: [0, 0, 100, 0, 100, 50], - occuded: true, + occluded: true, label: task.labels[0], group: 0, zOrder: 0, @@ -92,8 +91,7 @@ describe('Feature: put annotations', () => { await task.annotations.put([state]); annotations = await task.annotations.get(1); - expect(Array.isArray(annotations)).toBeTruthy(); - expect(annotations).toHaveLength(1); + expect(annotations).toHaveLength(length + 1); }); test('put annotations to a job', async () => { @@ -108,7 +106,7 @@ describe('Feature: put annotations', () => { shapeType: window.cvat.enums.ObjectShape.RECTANGLE, attributes: {}, points: [0, 0, 100, 100], - occuded: false, + occluded: false, label: job.task.labels[0], group: 0, zOrder: 0, @@ -153,19 +151,19 @@ describe('Feature: save annotations', () => { let annotations = await task.annotations.get(0); const { length } = annotations; const state = new window.cvat.classes.ObjectState({ - frame: 1, - objectType: window.cvat.enums.ObjectType.TRACK, + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, shapeType: window.cvat.enums.ObjectShape.POLYGON, attributes: {}, points: [0, 0, 100, 0, 100, 50], - occuded: true, + occluded: true, label: task.labels[0], group: 0, zOrder: 0, }); expect(await task.annotations.hasUnsavedChanges()).toBe(false); - await task.annotations.put(state); + await task.annotations.put([state]); expect(await task.annotations.hasUnsavedChanges()).toBe(true); await task.annotations.save(); expect(await task.annotations.hasUnsavedChanges()).toBe(false); @@ -201,19 +199,19 @@ describe('Feature: save annotations', () => { let annotations = await job.annotations.get(0); const { length } = annotations; const state = new window.cvat.classes.ObjectState({ - frame: 1, - objectType: window.cvat.enums.ObjectType.TRACK, + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, shapeType: window.cvat.enums.ObjectShape.POLYGON, attributes: {}, points: [0, 0, 100, 0, 100, 50], - occuded: true, + occluded: true, label: job.task.labels[0], group: 0, zOrder: 0, }); expect(await job.annotations.hasUnsavedChanges()).toBe(false); - await job.annotations.put(state); + await job.annotations.put([state]); expect(await job.annotations.hasUnsavedChanges()).toBe(true); await job.annotations.save(); expect(await job.annotations.hasUnsavedChanges()).toBe(false); From 8a9689b234f701f6dd43a8cc16432fba8a570b7f Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 11:02:28 +0300 Subject: [PATCH 06/16] Some written tests and some additional checks --- cvatjs/src/annotations-collection.js | 6 +- cvatjs/src/annotations-objects.js | 56 ++++++++- cvatjs/src/annotations-saver.js | 4 +- cvatjs/src/annotations.js | 12 +- cvatjs/src/common.js | 4 +- cvatjs/src/object-state.js | 10 +- cvatjs/src/session.js | 3 +- cvatjs/tests/api/annotations.js | 181 +++++++++++++++++++++++---- 8 files changed, 230 insertions(+), 46 deletions(-) diff --git a/cvatjs/src/annotations-collection.js b/cvatjs/src/annotations-collection.js index 6b0ecce37734..2af59368da54 100644 --- a/cvatjs/src/annotations-collection.js +++ b/cvatjs/src/annotations-collection.js @@ -526,7 +526,7 @@ } else if (object instanceof Tag) { objectType = 'tag'; } else { - throw window.cvat.exceptions.ScriptingError( + throw new window.cvat.exceptions.ScriptingError( `Unexpected object type: "${objectType}"`, ); } @@ -648,7 +648,7 @@ frame: state.frame, group: 0, label_id: state.label.id, - occluded: state.occluded, + occluded: state.occluded || false, points: [...state.points], type: state.shapeType, z_order: 0, @@ -664,7 +664,7 @@ attributes: attributes .filter(attr => labelAttributes[attr.spec_id].mutable), frame: state.frame, - occluded: state.occluded, + occluded: state.occluded || false, outside: false, points: [...state.points], type: state.shapeType, diff --git a/cvatjs/src/annotations-objects.js b/cvatjs/src/annotations-objects.js index 515bcc9cee29..d75aede990d9 100644 --- a/cvatjs/src/annotations-objects.js +++ b/cvatjs/src/annotations-objects.js @@ -25,6 +25,38 @@ return objectState; } + function checkNumberOfPoints(shapeType, points) { + if (shapeType === window.cvat.enums.ObjectShape.RECTANGLE) { + if (points.length / 2 !== 2) { + throw new window.cvat.exceptions.DataError( + `Rectangle must have 2 points, but got ${points.length / 2}`, + ); + } + } else if (shapeType === window.cvat.enums.ObjectShape.POLYGON) { + if (points.length / 2 < 3) { + throw new window.cvat.exceptions.DataError( + `Polygon must have at least 3 points, but got ${points.length / 2}`, + ); + } + } else if (shapeType === window.cvat.enums.ObjectShape.POLYLINE) { + if (points.length / 2 < 2) { + throw new window.cvat.exceptions.DataError( + `Polyline must have at least 2 points, but got ${points.length / 2}`, + ); + } + } else if (shapeType === window.cvat.enums.ObjectShape.POINTS) { + if (points.length / 2 < 1) { + throw new window.cvat.exceptions.DataError( + `Points must have at least 1 points, but got ${points.length / 2}`, + ); + } + } else { + throw new window.cvat.exceptions.ArgumentError( + `Unknown value of shapeType has been recieved ${shapeType}`, + ); + } + } + class Annotation { constructor(data, clientID, injection) { this.taskLabels = injection.labels; @@ -85,19 +117,19 @@ } save() { - throw window.cvat.exceptions.ScriptingError( + throw new window.cvat.exceptions.ScriptingError( 'Is not implemented', ); } get() { - throw window.cvat.exceptions.ScriptingError( + throw new window.cvat.exceptions.ScriptingError( 'Is not implemented', ); } toJSON() { - throw window.cvat.exceptions.ScriptingError( + throw new window.cvat.exceptions.ScriptingError( 'Is not implemented', ); } @@ -743,6 +775,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE; + checkNumberOfPoints(this.shapeType, this.points); } static distance(points, x, y) { @@ -768,6 +801,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POLYGON; + checkNumberOfPoints(this.shapeType, this.points); } static distance(points, x, y) { @@ -835,6 +869,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POLYLINE; + checkNumberOfPoints(this.shapeType, this.points); } static distance(points, x, y) { @@ -878,6 +913,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POINTS; + checkNumberOfPoints(this.shapeType, this.points); } static distance(points, x, y) { @@ -899,6 +935,9 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.RECTANGLE; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } } interpolatePosition(leftPosition, rightPosition, targetFrame) { @@ -1220,7 +1259,7 @@ if (!targetMatched.length) { // Prevent infinity loop - throw window.cvat.exceptions.ScriptingError('Interpolation mapping is empty'); + throw new window.cvat.exceptions.ScriptingError('Interpolation mapping is empty'); } while (!targetMatched.includes(prev)) { @@ -1310,6 +1349,9 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POLYGON; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } } } @@ -1317,6 +1359,9 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POLYLINE; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } } } @@ -1324,6 +1369,9 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = window.cvat.enums.ObjectShape.POINTS; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } } } diff --git a/cvatjs/src/annotations-saver.js b/cvatjs/src/annotations-saver.js index 55bb100a3f3b..49233646a3bb 100644 --- a/cvatjs/src/annotations-saver.js +++ b/cvatjs/src/annotations-saver.js @@ -102,7 +102,7 @@ } else if (typeof (object.id) === 'undefined') { splitted.created[type].push(object); } else { - throw window.cvat.exceptions.ScriptingError( + throw new window.cvat.exceptions.ScriptingError( `Id of object is defined "${object.id}"` + 'but it absents in initial state', ); @@ -140,7 +140,7 @@ + indexes.shapes.length + indexes.tags.length; if (indexesLength !== savedLength) { - throw window.cvat.exception.ScriptingError( + throw new window.cvat.exception.ScriptingError( 'Number of indexes is differed by number of saved objects' + `${indexesLength} vs ${savedLength}`, ); diff --git a/cvatjs/src/annotations.js b/cvatjs/src/annotations.js index 54c811cee980..b35fbf82c10e 100644 --- a/cvatjs/src/annotations.js +++ b/cvatjs/src/annotations.js @@ -73,7 +73,7 @@ return cache.get(session).collection.merge(objectStates); } - throw window.cvat.exceptions.DataError( + throw new window.cvat.exceptions.DataError( 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } @@ -86,7 +86,7 @@ return cache.get(session).collection.split(objectState, frame); } - throw window.cvat.exceptions.DataError( + throw new window.cvat.exceptions.DataError( 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } @@ -99,7 +99,7 @@ return cache.get(session).collection.group(objectStates, reset); } - throw window.cvat.exceptions.DataError( + throw new window.cvat.exceptions.DataError( 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } @@ -137,7 +137,7 @@ return cache.get(session).collection.statistics(); } - throw window.cvat.exceptions.DataError( + throw new window.cvat.exceptions.DataError( 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } @@ -150,7 +150,7 @@ return cache.get(session).collection.put(objectStates); } - throw window.cvat.exceptions.DataError( + throw new window.cvat.exceptions.DataError( 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } @@ -163,7 +163,7 @@ return cache.get(session).collection.select(objectStates, x, y); } - throw window.cvat.exceptions.DataError( + throw new window.cvat.exceptions.DataError( 'Collection has not been initialized yet. Call annotations.get() or annotations.clear(true) before', ); } diff --git a/cvatjs/src/common.js b/cvatjs/src/common.js index b92d67ff9dfd..dadbbf52ffb6 100644 --- a/cvatjs/src/common.js +++ b/cvatjs/src/common.js @@ -38,7 +38,7 @@ ); } else if (!fields[prop](filter[prop])) { throw new window.cvat.exceptions.ArgumentError( - `Received filter property ${prop} is not satisfied for checker`, + `Received filter property "${prop}" is not satisfied for checker`, ); } } @@ -61,7 +61,7 @@ if (!(value instanceof instance)) { if (value !== undefined) { throw new window.cvat.exceptions.ArgumentError( - `${name} is expected to be ${instance.name}, but ` + `"${name}" is expected to be ${instance.name}, but ` + `"${value.constructor.name}" has been got`, ); } diff --git a/cvatjs/src/object-state.js b/cvatjs/src/object-state.js index 669b9c7ce48e..1188a54fd140 100644 --- a/cvatjs/src/object-state.js +++ b/cvatjs/src/object-state.js @@ -157,8 +157,14 @@ */ get: () => data.points, set: (points) => { - data.updateFlags.points = true; - data.points = [...points]; + if (Array.isArray(points)) { + data.updateFlags.points = true; + data.points = [...points]; + } else { + throw new window.cvat.exceptions.ArgumentError( + `Points value must be an array, but got ${points.constructor.name}`, + ); + } }, }, group: { diff --git a/cvatjs/src/session.js b/cvatjs/src/session.js index 2ce6380eab38..fe0e0e89dc2f 100644 --- a/cvatjs/src/session.js +++ b/cvatjs/src/session.js @@ -259,6 +259,7 @@ * @param {module:API.cvat.classes.ObjectState[]} data * array of objects on the specific frame * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.DataError} * @throws {module:API.cvat.exceptions.ArgumentError} * @instance * @async @@ -659,7 +660,7 @@ return this; } - throw window.cvat.exceptions.ArgumentError( + throw new window.cvat.exceptions.ArgumentError( 'Can not save job without and id', ); }; diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index a4ae108d843f..dff3f19f032a 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -72,7 +72,7 @@ describe('Feature: get annotations', () => { describe('Feature: put annotations', () => { - test('put annotations to a task', async () => { + test('put a shape to a task', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; let annotations = await task.annotations.get(1); const { length } = annotations; @@ -81,12 +81,9 @@ describe('Feature: put annotations', () => { frame: 1, objectType: window.cvat.enums.ObjectType.SHAPE, shapeType: window.cvat.enums.ObjectShape.POLYGON, - attributes: {}, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], - group: 0, - zOrder: 0, }); await task.annotations.put([state]); @@ -94,31 +91,168 @@ describe('Feature: put annotations', () => { expect(annotations).toHaveLength(length + 1); }); - test('put annotations to a job', async () => { + test('put a shape to a job', async () => { const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; let annotations = await job.annotations.get(5); - expect(Array.isArray(annotations)).toBeTruthy(); - expect(annotations).toHaveLength(2); + const { length } = annotations; const state = new window.cvat.classes.ObjectState({ frame: 5, objectType: window.cvat.enums.ObjectType.SHAPE, shapeType: window.cvat.enums.ObjectShape.RECTANGLE, - attributes: {}, points: [0, 0, 100, 100], occluded: false, label: job.task.labels[0], - group: 0, - zOrder: 0, }); await job.annotations.put([state]); annotations = await job.annotations.get(5); - expect(Array.isArray(annotations)).toBeTruthy(); - expect(annotations).toHaveLength(3); + expect(annotations).toHaveLength(length + 1); + }); + + test('put a track to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + let annotations = await task.annotations.get(1); + const { length } = annotations; + + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + label: task.labels[0], + }); + + await task.annotations.put([state]); + annotations = await task.annotations.get(1); + expect(annotations).toHaveLength(length + 1); + }); + + test('put a track to a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + let annotations = await job.annotations.get(5); + const { length } = annotations; + + const state = new window.cvat.classes.ObjectState({ + frame: 5, + objectType: window.cvat.enums.ObjectType.TRACK, + shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + points: [0, 0, 100, 100], + occluded: false, + label: job.task.labels[0], + }); + + await job.annotations.put([state]); + annotations = await job.annotations.get(5); + expect(annotations).toHaveLength(length + 1); }); - // TODO: Put with invalid arguments (2-3 tests) + test('put object without objectType to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.clear(true); + const state = new window.cvat.classes.ObjectState({ + frame: 1, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + label: task.labels[0], + }); + + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('put shape with bad attributes to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.clear(true); + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + attributes: { 'bad key': 55 }, + occluded: true, + label: task.labels[0], + }); + + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('put shape without points and with invalud points to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.clear(true); + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + occluded: true, + points: [], + label: task.labels[0], + }); + + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.DataError); + + state.points = ['150,50 250,30']; + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('put shape without type to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.clear(true); + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.SHAPE, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + label: task.labels[0], + }); + + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('put shape without label and with bad label to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.clear(true); + const state = new window.cvat.classes.ObjectState({ + frame: 1, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + }); + + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.label = 'bad label'; + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.label = {}; + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('put shape with bad frame to a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + await task.annotations.clear(true); + const state = new window.cvat.classes.ObjectState({ + frame: '5', + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + label: task.labels[0], + }); + + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); }); describe('Feature: check unsaved changes', () => { @@ -154,12 +288,9 @@ describe('Feature: save annotations', () => { frame: 0, objectType: window.cvat.enums.ObjectType.SHAPE, shapeType: window.cvat.enums.ObjectShape.POLYGON, - attributes: {}, points: [0, 0, 100, 0, 100, 50], occluded: true, label: task.labels[0], - group: 0, - zOrder: 0, }); expect(await task.annotations.hasUnsavedChanges()).toBe(false); @@ -202,12 +333,9 @@ describe('Feature: save annotations', () => { frame: 0, objectType: window.cvat.enums.ObjectType.SHAPE, shapeType: window.cvat.enums.ObjectShape.POLYGON, - attributes: {}, points: [0, 0, 100, 0, 100, 50], occluded: true, label: job.task.labels[0], - group: 0, - zOrder: 0, }); expect(await job.annotations.hasUnsavedChanges()).toBe(false); @@ -252,7 +380,7 @@ describe('Feature: merge annotations', () => { }); - // TODO: merge with invalid parameters + // TODO: merge with bad parameters // Not object states // Created object state }); @@ -266,7 +394,7 @@ describe('Feature: split annotations', () => { }); - // TODO: split with invalid parameters (invalid frame, frame outside of shape etc.) + // TODO: split with bad parameters (bad frame, frame outside of shape etc.) }); describe('Feature: group annotations', () => { @@ -278,7 +406,7 @@ describe('Feature: group annotations', () => { }); - // TODO: group with invalid parameters (some values are invalid) + // TODO: group with bad parameters (some values are bad) }); describe('Feature: clear annotations', () => { @@ -298,7 +426,7 @@ describe('Feature: clear annotations', () => { }); - // TODO: clear with invalid parameter (not a boolean) + // TODO: clear with bad parameter (not a boolean) }); describe('Feature: get statistics', () => { @@ -320,12 +448,13 @@ describe('Feature: select object', () => { }); - // TODO: select with invalid parameters + // TODO: select with bad parameters // frame outside of range // frame is not a number - // invalid coordinates (not number) + // bad coordinates (not number) }); -// TODO: Tests for object state // TODO: Tests for frames +// TODO: Tests for plugins +// TODO: Tests for ObjectState.setAttributes, save(), setPoints From ebcc36e6daaf03dc5cfc57243cc201b6813b56eb Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 11:53:26 +0300 Subject: [PATCH 07/16] Merge tests --- cvatjs/src/annotations-collection.js | 28 ++++-- cvatjs/tests/api/annotations.js | 127 ++++++++++++++++++++++----- cvatjs/tests/api/frames.js | 1 + cvatjs/tests/api/jobs.js | 8 +- cvatjs/tests/api/object-state.js | 1 + cvatjs/tests/api/plugins.js | 1 + cvatjs/tests/api/server.js | 2 +- cvatjs/tests/api/tasks.js | 4 +- cvatjs/tests/api/user.js | 4 +- 9 files changed, 140 insertions(+), 36 deletions(-) create mode 100644 cvatjs/tests/api/frames.js create mode 100644 cvatjs/tests/api/object-state.js create mode 100644 cvatjs/tests/api/plugins.js diff --git a/cvatjs/src/annotations-collection.js b/cvatjs/src/annotations-collection.js index 2af59368da54..1c234c86a611 100644 --- a/cvatjs/src/annotations-collection.js +++ b/cvatjs/src/annotations-collection.js @@ -208,7 +208,7 @@ const object = this.objects[state.clientID]; if (typeof (object) === 'undefined') { throw new window.cvat.exceptions.ArgumentError( - 'The object has not been saved yet. Call ObjectState.save() before you can merge it', + 'The object has not been saved yet. Call ObjectState.put([state]) before you can merge it', ); } return object; @@ -216,6 +216,18 @@ const keyframes = {}; // frame: position const { label, shapeType } = objectStates[0]; + if (!(label.id in this.labels)) { + throw new window.cvat.exceptions.ArgumentError( + `Unknown label for the task: ${label.id}`, + ); + } + + if (!Object.values(window.cvat.enums.ObjectShape).includes(shapeType)) { + throw new window.cvat.exceptions.ArgumentError( + `Got unknown shapeType "${shapeType}"`, + ); + } + const labelAttributes = label.attributes.reduce((accumulator, attribute) => { accumulator[attribute.id] = attribute; return accumulator; @@ -365,7 +377,9 @@ // Remove other shapes for (const object of objectsForMerge) { object.removed = true; - object.resetCache(); + if (typeof (object.resetCache) === 'function') { + object.resetCache(); + } } } @@ -376,7 +390,7 @@ const object = this.objects[objectState.clientID]; if (typeof (object) === 'undefined') { throw new window.cvat.exceptions.ArgumentError( - 'The object has not been saved yet. Call annotations.put(state) before', + 'The object has not been saved yet. Call annotations.put([state]) before', ); } @@ -463,7 +477,7 @@ const object = this.objects[state.clientID]; if (typeof (object) === 'undefined') { throw new window.cvat.exceptions.ArgumentError( - 'The object has not been saved yet. Call annotations.put(state) before', + 'The object has not been saved yet. Call annotations.put([state]) before', ); } return object; @@ -472,7 +486,9 @@ const groupIdx = reset ? 0 : ++this.groups.max; for (const object of objectsForGroup) { object.group = groupIdx; - object.resetCache(); + if (typeof (object.resetCache) === 'function') { + object.resetCache(); + } } } @@ -698,7 +714,7 @@ const object = this.objects[state.clientID]; if (typeof (object) === 'undefined') { throw new window.cvat.exceptions.ArgumentError( - 'The object has not been saved yet. Call annotations.put(state) before', + 'The object has not been saved yet. Call annotations.put([state]) before', ); } diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index dff3f19f032a..7b93d61f64d3 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -47,11 +47,11 @@ describe('Feature: get annotations', () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; // Out of task - await expect(task.annotations.get(500)) + expect(task.annotations.get(500)) .rejects.toThrow(window.cvat.exceptions.ArgumentError); // Out of task - await expect(task.annotations.get(-1)) + expect(task.annotations.get(-1)) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -59,11 +59,11 @@ describe('Feature: get annotations', () => { const job = (await window.cvat.jobs.get({ jobID: 101 }))[0]; // Out of segment - await expect(job.annotations.get(500)) + expect(job.annotations.get(500)) .rejects.toThrow(window.cvat.exceptions.ArgumentError); // Out of segment - await expect(job.annotations.get(-1)) + expect(job.annotations.get(-1)) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -159,7 +159,7 @@ describe('Feature: put annotations', () => { label: task.labels[0], }); - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -176,7 +176,7 @@ describe('Feature: put annotations', () => { label: task.labels[0], }); - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -192,11 +192,11 @@ describe('Feature: put annotations', () => { label: task.labels[0], }); - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.DataError); state.points = ['150,50 250,30']; - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -211,7 +211,7 @@ describe('Feature: put annotations', () => { label: task.labels[0], }); - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -226,15 +226,15 @@ describe('Feature: put annotations', () => { occluded: true, }); - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); state.label = 'bad label'; - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); state.label = {}; - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -250,7 +250,7 @@ describe('Feature: put annotations', () => { label: task.labels[0], }); - await expect(task.annotations.put([state])) + expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); }); @@ -373,16 +373,106 @@ describe('Feature: save annotations', () => { describe('Feature: merge annotations', () => { test('merge annotations in a task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations0 = await task.annotations.get(0); + const annotations1 = await task.annotations.get(1); + const states = [annotations0[0], annotations1[0]]; + await task.annotations.merge(states); + const merged0 = (await task.annotations.get(0)) + .filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK); + const merged1 = (await task.annotations.get(1)) + .filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK); + expect(merged0).toHaveLength(1); + expect(merged1).toHaveLength(1); + expect(merged0[0].points).toEqual(states[0].points); + expect(merged1[0].points).toEqual(states[1].points); }); test('merge annotations in a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const annotations0 = await job.annotations.get(0); + const annotations1 = await job.annotations.get(1); + const states = [annotations0[0], annotations1[0]]; + await job.annotations.merge(states); + const merged0 = (await job.annotations.get(0)) + .filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK); + const merged1 = (await job.annotations.get(1)) + .filter(state => state.objectType === window.cvat.enums.ObjectType.TRACK); + expect(merged0).toHaveLength(1); + expect(merged1).toHaveLength(1); + + expect(merged0[0].points).toEqual(states[0].points); + expect(merged1[0].points).toEqual(states[1].points); + }); + + test('trying to merge not object state', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations0 = await task.annotations.get(0); + const states = [annotations0[0], {}]; + + expect(task.annotations.merge(states)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('trying to merge object state which is not saved in a collection', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations0 = await task.annotations.get(0); + + const state = new window.cvat.classes.ObjectState({ + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + label: task.labels[0], + }); + const states = [annotations0[0], state]; + + expect(task.annotations.merge(states)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('trying to merge with bad label', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations0 = await task.annotations.get(0); + const annotations1 = await task.annotations.get(1); + const states = [annotations0[0], annotations1[0]]; + states[0].label = new window.cvat.classes.Label({ + id: 500, + name: 'new_label', + attributes: [], + }); + + expect(task.annotations.merge(states)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + test('trying to merge with different shape types', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations0 = await task.annotations.get(0); + const annotations1 = (await task.annotations.get(1)) + .filter(state => state.shapeType === window.cvat.enums.ObjectShape.POLYGON); + const states = [annotations0[0], annotations1[0]]; + + expect(task.annotations.merge(states)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); - // TODO: merge with bad parameters - // Not object states - // Created object state + test('trying to merge with different labels', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations0 = await task.annotations.get(0); + const annotations1 = await task.annotations.get(1); + const states = [annotations0[0], annotations1[0]]; + states[1].label = new window.cvat.classes.Label({ + id: 500, + name: 'new_label', + attributes: [], + }); + + expect(task.annotations.merge(states)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); }); describe('Feature: split annotations', () => { @@ -453,8 +543,3 @@ describe('Feature: select object', () => { // frame is not a number // bad coordinates (not number) }); - - -// TODO: Tests for frames -// TODO: Tests for plugins -// TODO: Tests for ObjectState.setAttributes, save(), setPoints diff --git a/cvatjs/tests/api/frames.js b/cvatjs/tests/api/frames.js new file mode 100644 index 000000000000..fd00b032c638 --- /dev/null +++ b/cvatjs/tests/api/frames.js @@ -0,0 +1 @@ +// TODO: Tests for frames diff --git a/cvatjs/tests/api/jobs.js b/cvatjs/tests/api/jobs.js index 6075389a83b3..9fc48132a2e8 100644 --- a/cvatjs/tests/api/jobs.js +++ b/cvatjs/tests/api/jobs.js @@ -64,26 +64,26 @@ describe('Feature: get a list of jobs', () => { }); test('get jobs by invalid filter with both taskID and jobID', async () => { - await expect(window.cvat.jobs.get({ + expect(window.cvat.jobs.get({ taskID: 1, jobID: 1, })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); test('get jobs by invalid job id', async () => { - await expect(window.cvat.jobs.get({ + expect(window.cvat.jobs.get({ jobID: '1', })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); test('get jobs by invalid task id', async () => { - await expect(window.cvat.jobs.get({ + expect(window.cvat.jobs.get({ taskID: '1', })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); test('get jobs by unknown filter', async () => { - await expect(window.cvat.jobs.get({ + expect(window.cvat.jobs.get({ unknown: 50, })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); diff --git a/cvatjs/tests/api/object-state.js b/cvatjs/tests/api/object-state.js new file mode 100644 index 000000000000..75e219236add --- /dev/null +++ b/cvatjs/tests/api/object-state.js @@ -0,0 +1 @@ +// TODO: Tests for ObjectState.setAttributes, save(), setPoints diff --git a/cvatjs/tests/api/plugins.js b/cvatjs/tests/api/plugins.js new file mode 100644 index 000000000000..e509ed3f77de --- /dev/null +++ b/cvatjs/tests/api/plugins.js @@ -0,0 +1 @@ +// TODO: Tests for plugins diff --git a/cvatjs/tests/api/server.js b/cvatjs/tests/api/server.js index 13abc9c4560f..b6f38c8f2bc5 100644 --- a/cvatjs/tests/api/server.js +++ b/cvatjs/tests/api/server.js @@ -44,7 +44,7 @@ describe('Feature: get share storage info', () => { }); test('get files in a some unknown dir of a share storage', async () => { - await expect(window.cvat.server.share( + expect(window.cvat.server.share( 'Unknown Directory', )).rejects.toThrow(window.cvat.exceptions.ServerError); }); diff --git a/cvatjs/tests/api/tasks.js b/cvatjs/tests/api/tasks.js index c1b1e7210216..968cab698300 100644 --- a/cvatjs/tests/api/tasks.js +++ b/cvatjs/tests/api/tasks.js @@ -51,7 +51,7 @@ describe('Feature: get a list of tasks', () => { }); test('get a task by an invalid id', async () => { - await expect(window.cvat.tasks.get({ + expect(window.cvat.tasks.get({ id: '50', })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -69,7 +69,7 @@ describe('Feature: get a list of tasks', () => { }); test('get tasks by invalid filters', async () => { - await expect(window.cvat.tasks.get({ + expect(window.cvat.tasks.get({ unknown: '5', })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); diff --git a/cvatjs/tests/api/user.js b/cvatjs/tests/api/user.js index 9b098c1b7e2d..d763ebd2e1f4 100644 --- a/cvatjs/tests/api/user.js +++ b/cvatjs/tests/api/user.js @@ -41,13 +41,13 @@ describe('Feature: get a list of users', () => { }); test('get users with unknown filter key', async () => { - await expect(window.cvat.users.get({ + expect(window.cvat.users.get({ unknown: '50', })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); test('get users with invalid filter key', async () => { - await expect(window.cvat.users.get({ + expect(window.cvat.users.get({ self: 1, })).rejects.toThrow(window.cvat.exceptions.ArgumentError); }); From f2bc421ad8d33be0299f6afedf8484a20f0a9dbf Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 12:39:58 +0300 Subject: [PATCH 08/16] Split & group tests --- cvatjs/src/annotations-collection.js | 4 +- cvatjs/tests/api/annotations.js | 66 ++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/cvatjs/src/annotations-collection.js b/cvatjs/src/annotations-collection.js index 1c234c86a611..750ff85a3880 100644 --- a/cvatjs/src/annotations-collection.js +++ b/cvatjs/src/annotations-collection.js @@ -399,7 +399,7 @@ } const keyframes = Object.keys(object.shapes).sort((a, b) => +a - +b); - if (frame <= +keyframes[0]) { + if (frame <= +keyframes[0] || frame > keyframes[keyframes.length - 1]) { return; } @@ -490,6 +490,8 @@ object.resetCache(); } } + + return groupIdx; } clear() { diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index 7b93d61f64d3..8716b97b4153 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -477,26 +477,86 @@ describe('Feature: merge annotations', () => { describe('Feature: split annotations', () => { test('split annotations in a task', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations4 = await task.annotations.get(4); + const annotations5 = await task.annotations.get(5); + expect(annotations4[0].clientID).toBe(annotations5[0].clientID); + await task.annotations.split(annotations5[0], 5); + const splitted4 = await task.annotations.get(4); + const splitted5 = (await task.annotations.get(5)).filter(state => !state.outside); + expect(splitted4[0].clientID).not.toBe(splitted5[0].clientID); }); test('split annotations in a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 101 }))[0]; + const annotations4 = await job.annotations.get(4); + const annotations5 = await job.annotations.get(5); + expect(annotations4[0].clientID).toBe(annotations5[0].clientID); + await job.annotations.split(annotations5[0], 5); + const splitted4 = await job.annotations.get(4); + const splitted5 = (await job.annotations.get(5)).filter(state => !state.outside); + expect(splitted4[0].clientID).not.toBe(splitted5[0].clientID); }); - // TODO: split with bad parameters (bad frame, frame outside of shape etc.) + test('split on a bad frame', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations4 = await task.annotations.get(4); + const annotations5 = await task.annotations.get(5); + + expect(annotations4[0].clientID).toBe(annotations5[0].clientID); + expect(task.annotations.split(annotations5[0], 'bad frame')) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); }); describe('Feature: group annotations', () => { test('group annotations in a task', async () => { - + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + let annotations = await task.annotations.get(0); + const groupID = await task.annotations.group(annotations); + expect(typeof (groupID)).toBe('number'); + annotations = await task.annotations.get(0); + for (const state of annotations) { + expect(state.group).toBe(groupID); + } }); test('group annotations in a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + let annotations = await job.annotations.get(0); + const groupID = await job.annotations.group(annotations); + expect(typeof (groupID)).toBe('number'); + annotations = await job.annotations.get(0); + for (const state of annotations) { + expect(state.group).toBe(groupID); + } + }); + test('trying to group object state which has not been saved in a collection', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + await task.annotations.clear(true); + + const state = new window.cvat.classes.ObjectState({ + frame: 0, + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.POLYGON, + points: [0, 0, 100, 0, 100, 50], + occluded: true, + label: task.labels[0], + }); + + expect(task.annotations.group([state])) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); - // TODO: group with bad parameters (some values are bad) + test('trying to group not object state', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + expect(task.annotations.group(annotations.concat({}))) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); }); describe('Feature: clear annotations', () => { From 771041485c33a92110474efc08b1238530de2748 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 12:56:59 +0300 Subject: [PATCH 09/16] Clear annotations tests --- cvatjs/src/annotations.js | 2 ++ cvatjs/tests/api/annotations.js | 51 +++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/cvatjs/src/annotations.js b/cvatjs/src/annotations.js index b35fbf82c10e..766c71eff5c1 100644 --- a/cvatjs/src/annotations.js +++ b/cvatjs/src/annotations.js @@ -11,6 +11,7 @@ const serverProxy = require('./server-proxy'); const Collection = require('./annotations-collection'); const AnnotationsSaver = require('./annotations-saver'); + const { checkObjectType } = require('./common'); const jobCache = new WeakMap(); const taskCache = new WeakMap(); @@ -116,6 +117,7 @@ } async function clearAnnotations(session, reload) { + checkObjectType('reload', reload, 'boolean', null); const sessionType = session instanceof window.cvat.classes.Task ? 'task' : 'job'; const cache = getCache(sessionType); diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index 8716b97b4153..b0f512b4e70c 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -192,7 +192,7 @@ describe('Feature: put annotations', () => { label: task.labels[0], }); - expect(task.annotations.put([state])) + await expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.DataError); state.points = ['150,50 250,30']; @@ -226,15 +226,15 @@ describe('Feature: put annotations', () => { occluded: true, }); - expect(task.annotations.put([state])) + await expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); state.label = 'bad label'; - expect(task.annotations.put([state])) + await expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); state.label = {}; - expect(task.annotations.put([state])) + await expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); @@ -561,22 +561,55 @@ describe('Feature: group annotations', () => { describe('Feature: clear annotations', () => { test('clear annotations in a task', async () => { - + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + let annotations = await task.annotations.get(0); + expect(annotations.length).not.toBe(0); + await task.annotations.clear(); + annotations = await task.annotations.get(0); + expect(annotations.length).toBe(0); }); test('clear annotations in a job', async () => { - + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + let annotations = await job.annotations.get(0); + expect(annotations.length).not.toBe(0); + await job.annotations.clear(); + annotations = await job.annotations.get(0); + expect(annotations.length).toBe(0); }); test('clear annotations with reload in a task', async () => { - + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + let annotations = await task.annotations.get(0); + expect(annotations.length).not.toBe(0); + annotations[0].occluded = true; + await annotations[0].save(); + expect(await task.annotations.hasUnsavedChanges()).toBe(true); + await task.annotations.clear(true); + annotations = await task.annotations.get(0); + expect(annotations.length).not.toBe(0); + expect(await task.annotations.hasUnsavedChanges()).toBe(false); }); test('clear annotations with reload in a job', async () => { - + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + let annotations = await job.annotations.get(0); + expect(annotations.length).not.toBe(0); + annotations[0].occluded = true; + await annotations[0].save(); + expect(await job.annotations.hasUnsavedChanges()).toBe(true); + await job.annotations.clear(true); + annotations = await job.annotations.get(0); + expect(annotations.length).not.toBe(0); + expect(await job.annotations.hasUnsavedChanges()).toBe(false); }); - // TODO: clear with bad parameter (not a boolean) + test('clear annotations with bad reload parameter', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + await task.annotations.clear(true); + expect(task.annotations.clear('reload')) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); }); describe('Feature: get statistics', () => { From 1bd111178a87e1e91f52d356d9e19a6653ae93f5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 12:59:23 +0300 Subject: [PATCH 10/16] Statistics tests --- cvatjs/tests/api/annotations.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index b0f512b4e70c..c79b1a91dbce 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -614,11 +614,17 @@ describe('Feature: clear annotations', () => { describe('Feature: get statistics', () => { test('get statistics from a task', async () => { - + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + await task.annotations.clear(true); + const statistics = await task.annotations.statistics(); + expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); }); test('get statistics from a job', async () => { - + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + await job.annotations.clear(true); + const statistics = await job.annotations.statistics(); + expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); }); }); From 8e2204a7d0e8e89de100d134cbb51ab959d523e6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 13:39:25 +0300 Subject: [PATCH 11/16] Added frames meta --- cvatjs/src/annotations-collection.js | 16 +++++- cvatjs/src/annotations.js | 19 ++++++- cvatjs/tests/api/annotations.js | 4 +- cvatjs/tests/mocks/dummy-data.mock.js | 74 ++++++++++++++++++++++++- cvatjs/tests/mocks/server-proxy.mock.js | 3 +- 5 files changed, 109 insertions(+), 7 deletions(-) diff --git a/cvatjs/src/annotations-collection.js b/cvatjs/src/annotations-collection.js index 750ff85a3880..c49d6506ad3f 100644 --- a/cvatjs/src/annotations-collection.js +++ b/cvatjs/src/annotations-collection.js @@ -104,8 +104,12 @@ } class Collection { - constructor(labels) { - this.labels = labels.reduce((labelAccumulator, label) => { + constructor(data) { + this.startFrame = data.startFrame; + this.stopFrame = data.stopFrame; + this.frameMeta = data.startFrame; + + this.labels = data.labels.reduce((labelAccumulator, label) => { labelAccumulator[label.id] = label; return labelAccumulator; }, {}); @@ -561,6 +565,7 @@ if (objectType === 'track') { const keyframes = Object.keys(object.shapes) .sort((a, b) => +a - +b).map(el => +el); + let prevKeyframe = keyframes[0]; let visible = false; @@ -578,6 +583,13 @@ labels[label].total++; } } + + const lastKey = keyframes[keyframes.length - 1]; + if (lastKey !== this.stopFrame && !object.shapes[lastKey].outside) { + const interpolated = this.stopFrame - lastKey; + labels[label].interpolated += interpolated; + labels[label].total += interpolated; + } } else { labels[label].manually++; labels[label].total++; diff --git a/cvatjs/src/annotations.js b/cvatjs/src/annotations.js index 766c71eff5c1..dd8edb6feefc 100644 --- a/cvatjs/src/annotations.js +++ b/cvatjs/src/annotations.js @@ -37,13 +37,28 @@ if (!cache.has(session)) { const rawAnnotations = await serverProxy.annotations .getAnnotations(sessionType, session.id); - const collection = new Collection(session.labels || session.task.labels) - .import(rawAnnotations); + + // Get meta information about frames + const startFrame = sessionType === 'job' ? session.startFrame : 0; + const stopFrame = sessionType === 'job' ? session.stopFrame : session.size - 1; + const frameMeta = {}; + for (let i = startFrame; i <= stopFrame; i++) { + frameMeta[i] = await session.frames.get(i); + } + + const collection = new Collection({ + labels: session.labels || session.task.labels, + startFrame, + stopFrame, + frameMeta, + }).import(rawAnnotations); + const saver = new AnnotationsSaver(rawAnnotations.version, collection, session); cache.set(session, { collection, saver, + }); } } diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index c79b1a91dbce..8e2a4c4f7b2d 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -618,13 +618,15 @@ describe('Feature: get statistics', () => { await task.annotations.clear(true); const statistics = await task.annotations.statistics(); expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); + expect(statistics.total.total).toBe(29); }); test('get statistics from a job', async () => { - const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const job = (await window.cvat.jobs.get({ jobID: 101 }))[0]; await job.annotations.clear(true); const statistics = await job.annotations.statistics(); expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); + expect(statistics.total.total).toBe(512); }); }); diff --git a/cvatjs/tests/mocks/dummy-data.mock.js b/cvatjs/tests/mocks/dummy-data.mock.js index 349290fda037..065b34e6c4d5 100644 --- a/cvatjs/tests/mocks/dummy-data.mock.js +++ b/cvatjs/tests/mocks/dummy-data.mock.js @@ -2366,6 +2366,77 @@ const taskAnnotationsDummyData = { const jobAnnotationsDummyData = JSON.parse(JSON.stringify(taskAnnotationsDummyData)); +const frameMetaDummyData = { + 1: [{ + "width": 1920, + "height": 1080 + }, { + "width": 1600, + "height": 1143 + }, { + "width": 1600, + "height": 859 + }, { + "width": 3840, + "height": 2160 + }, { + "width": 2560, + "height": 1920 + }, { + "width": 1920, + "height": 1080 + }, { + "width": 1920, + "height": 1080 + }, { + "width": 700, + "height": 453 + }, { + "width": 1920, + "height": 1200 + }], + 2: [{ + "width": 1920, + "height": 1080 + }], + 3: [{ + "width": 1888, + "height": 1408 + }], + 100: [{ + "width": 1920, + "height": 1080 + }, { + "width": 1600, + "height": 1143 + }, { + "width": 1600, + "height": 859 + }, { + "width": 3840, + "height": 2160 + }, { + "width": 2560, + "height": 1920 + }, { + "width": 1920, + "height": 1080 + }, { + "width": 1920, + "height": 1080 + }, { + "width": 700, + "height": 453 + }, { + "width": 1920, + "height": 1200 + }], + 101: [{ + "width": 1888, + "height": 1408 + }], +} + module.exports = { tasksDummyData, aboutDummyData, @@ -2373,4 +2444,5 @@ module.exports = { usersDummyData, taskAnnotationsDummyData, jobAnnotationsDummyData, -} \ No newline at end of file + frameMetaDummyData, +} diff --git a/cvatjs/tests/mocks/server-proxy.mock.js b/cvatjs/tests/mocks/server-proxy.mock.js index cbc6ae55042c..ddf6cb8c7827 100644 --- a/cvatjs/tests/mocks/server-proxy.mock.js +++ b/cvatjs/tests/mocks/server-proxy.mock.js @@ -16,6 +16,7 @@ const { usersDummyData, taskAnnotationsDummyData, jobAnnotationsDummyData, + frameMetaDummyData, } = require('./dummy-data.mock'); @@ -192,7 +193,7 @@ class ServerProxy { } async function getMeta(tid) { - return null; + return JSON.parse(JSON.stringify(frameMetaDummyData[tid])); } async function getAnnotations(session, id) { From 0aff7b7446f108b6d053fd90eb4294db4e2b50b7 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 18 Jul 2019 15:59:11 +0300 Subject: [PATCH 12/16] Selection tests & bug fixes --- cvatjs/src/annotations-objects.js | 45 ++++++++++++++++------------- cvatjs/src/session.js | 6 ++-- cvatjs/tests/api/annotations.js | 47 +++++++++++++++++++++++++++---- 3 files changed, 71 insertions(+), 27 deletions(-) diff --git a/cvatjs/src/annotations-objects.js b/cvatjs/src/annotations-objects.js index d75aede990d9..1d30a0862bde 100644 --- a/cvatjs/src/annotations-objects.js +++ b/cvatjs/src/annotations-objects.js @@ -806,19 +806,20 @@ static distance(points, x, y) { function position(x1, y1, x2, y2) { - return ((x1 - x) * (y2 - y) - (x2 - x) * (y1 - y)); + return ((x2 - x1) * (y - y1) - (x - x1) * (y2 - y1)); } let wn = 0; const distances = []; - for (let i = 0; i < points.length; i += 2) { + + for (let i = 0, j = points.length - 2; i < points.length - 1; j = i, i += 2) { // Current point - const x1 = points[i]; - const y1 = points[i + 1]; + const x1 = points[j]; + const y1 = points[j + 1]; // Next point - const x2 = i + 2 < points.length ? points[i + 2] : points[0]; - const y2 = i + 3 < points.length ? points[i + 3] : points[1]; + const x2 = points[i]; + const y2 = points[i + 1]; // Check if a point is inside a polygon // with a winding numbers algorithm @@ -829,25 +830,31 @@ wn++; } } - } else if (y2 < y) { - if (position(x1, y1, x2, y2) > 0) { + } else if (y2 <= y) { + if (position(x1, y1, x2, y2) < 0) { wn--; } } // Find the shortest distance from point to an edge - if (((x - x1) * (x2 - x)) >= 0 && ((y - y1) * (y2 - y)) >= 0) { - // Find the length of a perpendicular - // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line - distances.push( - Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / Math - .sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)), - ); + // Get an equation of a line in general + const aCoef = (y1 - y2); + const bCoef = (x2 - x1); + + // Vector (aCoef, bCoef) is a perpendicular to line + // Now find the point where two lines + // (edge and its perpendicular through the point (x,y)) are cross + const xCross = x - aCoef; + const yCross = y - bCoef; + + if (((xCross - x1) * (x2 - xCross)) >= 0 + && ((yCross - y1) * (y2 - yCross)) >= 0) { + // Cross point is on segment between p1(x1,y1) and p2(x2,y2) + distances.push(Math.sqrt( + Math.pow(x - xCross, 2) + + Math.pow(y - yCross, 2), + )); } else { - // The link below works for lines (which have infinit length) - // There is a case when perpendicular doesn't cross the edge - // In this case we don't use the computed distance - // Instead we use just distance to the nearest point distances.push( Math.min( Math.sqrt(Math.pow(x1 - x, 2) + Math.pow(y1 - y, 2)), diff --git a/cvatjs/src/session.js b/cvatjs/src/session.js index fe0e0e89dc2f..2e65e0d3a44a 100644 --- a/cvatjs/src/session.js +++ b/cvatjs/src/session.js @@ -303,11 +303,13 @@ * @async */ /** - * Select shape under a cursor using math alghorithms + * Select shape under a cursor by using minimal distance + * between a cursor and a shape edge or a shape point + * For closed shapes a cursor is placed inside a shape * @method select * @memberof Session.annotations * @param {module:API.cvat.classes.ObjectState[]} objectStates - * object which can be selected + * objects which can be selected * @param {float} x horizontal coordinate * @param {float} y vertical coordinate * @returns {Object} diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index 8e2a4c4f7b2d..ab0ede5f1ecf 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -632,15 +632,50 @@ describe('Feature: get statistics', () => { describe('Feature: select object', () => { test('select object in a task', async () => { - + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + let result = await task.annotations.select(annotations, 1430, 765); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + result = await task.annotations.select(annotations, 1415, 765); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); + expect(result.state.points.length).toBe(10); + result = await task.annotations.select(annotations, 1083, 543); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POINTS); + expect(result.state.points.length).toBe(16); + result = await task.annotations.select(annotations, 613, 811); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); + expect(result.state.points.length).toBe(94); }); test('select object in a job', async () => { - + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const annotations = await job.annotations.get(0); + let result = await job.annotations.select(annotations, 490, 540); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + result = await job.annotations.select(annotations, 430, 260); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYLINE); + result = await job.annotations.select(annotations, 1473, 250); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + result = await job.annotations.select(annotations, 1490, 237); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); + expect(result.state.points.length).toBe(94); + }); + + test('trying to select from not object states', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + expect(task.annotations.select(annotations.concat({}), 500, 500)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); - // TODO: select with bad parameters - // frame outside of range - // frame is not a number - // bad coordinates (not number) + test('trying to select with invalid coordinates', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + expect(task.annotations.select(annotations, null, null)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + expect(task.annotations.select(annotations, null, null)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + expect(task.annotations.select(annotations, '5', '10')) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); }); From 663a324abd3bbe4a1df6777eebea117a3112fdb6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 19 Jul 2019 11:41:51 +0300 Subject: [PATCH 13/16] Tests for frame --- cvatjs/src/session.js | 10 ++- cvatjs/tests/api/frames.js | 72 +++++++++++++++++- cvatjs/tests/api/object-state.js | 38 +++++++++- cvatjs/tests/api/plugins.js | 99 ++++++++++++++++++++++++- cvatjs/tests/api/tasks.js | 74 ------------------ cvatjs/tests/mocks/server-proxy.mock.js | 4 +- 6 files changed, 216 insertions(+), 81 deletions(-) diff --git a/cvatjs/src/session.js b/cvatjs/src/session.js index 2e65e0d3a44a..5c764eb45eec 100644 --- a/cvatjs/src/session.js +++ b/cvatjs/src/session.js @@ -676,7 +676,7 @@ if (frame < this.startFrame || frame > this.stopFrame) { throw new window.cvat.exceptions.ArgumentError( - `Frame ${frame} does not exist in the job`, + `The frame with number ${frame} is out of the job`, ); } @@ -1265,9 +1265,15 @@ }; Task.prototype.frames.get.implementation = async function (frame) { + if (!Number.isInteger(frame) || frame < 0) { + throw new window.cvat.exceptions.ArgumentError( + `Frame must be a positive integer. Got: "${frame}"`, + ); + } + if (frame >= this.size) { throw new window.cvat.exceptions.ArgumentError( - `Frame ${frame} does not exist in the task`, + `The frame with number ${frame} is out of the task`, ); } diff --git a/cvatjs/tests/api/frames.js b/cvatjs/tests/api/frames.js index fd00b032c638..b944ea23d2dd 100644 --- a/cvatjs/tests/api/frames.js +++ b/cvatjs/tests/api/frames.js @@ -1 +1,71 @@ -// TODO: Tests for frames +/* + * Copyright (C) 2018 Intel Corporation + * SPDX-License-Identifier: MIT +*/ + +/* global + require:false + jest:false + describe:false +*/ + +// Setup mock for a server +jest.mock('../../src/server-proxy', () => { + const mock = require('../mocks/server-proxy.mock'); + return mock; +}); + +// Initialize api +require('../../src/api'); + +const { FrameData } = require('../../src/frames'); + +describe('Feature: get frame meta', () => { + test('get meta for a task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const frame = await task.frames.get(0); + expect(frame).toBeInstanceOf(FrameData); + }); + + test('get meta for a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const frame = await job.frames.get(0); + expect(frame).toBeInstanceOf(FrameData); + }); + + test('pass frame number out of a task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + expect(task.frames.get(100)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + expect(task.frames.get(-1)) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('pass bad frame number', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + expect(task.frames.get('5')) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('do not pass any frame number', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + expect(task.frames.get()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); +}); + +describe('Feature: get frame data', () => { + test('get meta for a task', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const frame = await task.frames.get(0); + const frameData = await frame.frame(); + expect(typeof (frameData)).toBe('string'); + }); + + test('get meta for a job', async () => { + const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; + const frame = await job.frames.get(0); + const frameData = await frame.frame(); + expect(typeof (frameData)).toBe('string'); + }); +}); diff --git a/cvatjs/tests/api/object-state.js b/cvatjs/tests/api/object-state.js index 75e219236add..5963ccd4ab1d 100644 --- a/cvatjs/tests/api/object-state.js +++ b/cvatjs/tests/api/object-state.js @@ -1 +1,37 @@ -// TODO: Tests for ObjectState.setAttributes, save(), setPoints +/* + * Copyright (C) 2018 Intel Corporation + * SPDX-License-Identifier: MIT +*/ + +/* global + require:false + jest:false + describe:false +*/ + +// Setup mock for a server +jest.mock('../../src/server-proxy', () => { + const mock = require('../mocks/server-proxy.mock'); + return mock; +}); + +// Initialize api +require('../../src/api'); + +describe('Feature: set attributes for object state', () => { + test('get meta for a job', async () => { + + }); +}); + +describe('Feature: set points for object state', () => { + test('get meta for a task', async () => { + + }); +}); + +describe('Feature: save object state', () => { + test('get meta for a task', async () => { + + }); +}); diff --git a/cvatjs/tests/api/plugins.js b/cvatjs/tests/api/plugins.js index e509ed3f77de..92ba06c12f14 100644 --- a/cvatjs/tests/api/plugins.js +++ b/cvatjs/tests/api/plugins.js @@ -1 +1,98 @@ -// TODO: Tests for plugins +/* + * Copyright (C) 2018 Intel Corporation + * SPDX-License-Identifier: MIT +*/ + +/* global + require:false + jest:false + describe:false +*/ + +// Setup mock for a server +jest.mock('../../src/server-proxy', () => { + const mock = require('../mocks/server-proxy.mock'); + return mock; +}); + +// Initialize api +require('../../src/api'); + +describe('Feature: dummy feature', () => { + test('dummy test', async () => { + // TODO: Write test after design of plugin system + }); +}); + +/* +const plugin = { + name: 'Example Plugin', + description: 'This example plugin demonstrates how plugin system in CVAT works', + cvat: { + server: { + about: { + async leave(self, result) { + result.plugins = await self.internal.getPlugins(); + return result; + }, + }, + }, + classes: { + Job: { + prototype: { + annotations: { + put: { + enter(self, objects) { + for (const obj of objects) { + if (obj.type !== 'tag') { + const points = obj.position.map((point) => { + const roundPoint = { + x: Math.round(point.x), + y: Math.round(point.y), + }; + return roundPoint; + }); + obj.points = points; + } + } + }, + }, + }, + }, + }, + }, + }, + internal: { + async getPlugins() { + const plugins = await window.cvat.plugins.list(); + return plugins.map((el) => { + const obj = { + name: el.name, + description: el.description, + }; + return obj; + }); + }, + }, +}; + + +async function test() { + await window.cvat.plugins.register(plugin); + await window.cvat.server.login('admin', 'nimda760'); + + try { + console.log(JSON.stringify(await window.cvat.server.about())); + console.log(await window.cvat.users.get({ self: false })); + console.log(await window.cvat.users.get({ self: true })); + console.log(JSON.stringify(await window.cvat.jobs.get({ taskID: 8 }))); + console.log(JSON.stringify(await window.cvat.jobs.get({ jobID: 10 }))); + console.log(await window.cvat.tasks.get()); + console.log(await window.cvat.tasks.get({ id: 8 })); + console.log('Done.'); + } catch (exception) { + console.log(exception.constructor.name); + console.log(exception.message); + } +} +*/ diff --git a/cvatjs/tests/api/tasks.js b/cvatjs/tests/api/tasks.js index 968cab698300..030ae6e0b7da 100644 --- a/cvatjs/tests/api/tasks.js +++ b/cvatjs/tests/api/tasks.js @@ -184,77 +184,3 @@ describe('Feature: delete a task', () => { expect(result).toHaveLength(0); }); }); - - -/* -const plugin = { - name: 'Example Plugin', - description: 'This example plugin demonstrates how plugin system in CVAT works', - cvat: { - server: { - about: { - async leave(self, result) { - result.plugins = await self.internal.getPlugins(); - return result; - }, - }, - }, - classes: { - Job: { - prototype: { - annotations: { - put: { - enter(self, objects) { - for (const obj of objects) { - if (obj.type !== 'tag') { - const points = obj.position.map((point) => { - const roundPoint = { - x: Math.round(point.x), - y: Math.round(point.y), - }; - return roundPoint; - }); - obj.points = points; - } - } - }, - }, - }, - }, - }, - }, - }, - internal: { - async getPlugins() { - const plugins = await window.cvat.plugins.list(); - return plugins.map((el) => { - const obj = { - name: el.name, - description: el.description, - }; - return obj; - }); - }, - }, -}; - - -async function test() { - await window.cvat.plugins.register(plugin); - await window.cvat.server.login('admin', 'nimda760'); - - try { - console.log(JSON.stringify(await window.cvat.server.about())); - console.log(await window.cvat.users.get({ self: false })); - console.log(await window.cvat.users.get({ self: true })); - console.log(JSON.stringify(await window.cvat.jobs.get({ taskID: 8 }))); - console.log(JSON.stringify(await window.cvat.jobs.get({ jobID: 10 }))); - console.log(await window.cvat.tasks.get()); - console.log(await window.cvat.tasks.get({ id: 8 })); - console.log('Done.'); - } catch (exception) { - console.log(exception.constructor.name); - console.log(exception.message); - } -} -*/ diff --git a/cvatjs/tests/mocks/server-proxy.mock.js b/cvatjs/tests/mocks/server-proxy.mock.js index ddf6cb8c7827..6785e9a2615e 100644 --- a/cvatjs/tests/mocks/server-proxy.mock.js +++ b/cvatjs/tests/mocks/server-proxy.mock.js @@ -188,8 +188,8 @@ class ServerProxy { return JSON.parse(JSON.stringify(usersDummyData)).results[0]; } - async function getFrame(tid, frame) { - return null; + async function getFrame() { + return 'DUMMY_IMAGE'; } async function getMeta(tid) { From de32df8424977daa61e284d7e1d8ba755162764c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 19 Jul 2019 14:37:56 +0300 Subject: [PATCH 14/16] ObjectState tests, many fixed bugs --- cvatjs/src/annotations-collection.js | 3 +- cvatjs/src/annotations-objects.js | 182 +++++++++++++++----- cvatjs/src/api.js | 9 +- cvatjs/src/object-state.js | 19 ++- cvatjs/tests/api/annotations.js | 4 + cvatjs/tests/api/object-state.js | 247 ++++++++++++++++++++++++++- 6 files changed, 404 insertions(+), 60 deletions(-) diff --git a/cvatjs/src/annotations-collection.js b/cvatjs/src/annotations-collection.js index c49d6506ad3f..fc03e725509f 100644 --- a/cvatjs/src/annotations-collection.js +++ b/cvatjs/src/annotations-collection.js @@ -107,7 +107,7 @@ constructor(data) { this.startFrame = data.startFrame; this.stopFrame = data.stopFrame; - this.frameMeta = data.startFrame; + this.frameMeta = data.frameMeta; this.labels = data.labels.reduce((labelAccumulator, label) => { labelAccumulator[label.id] = label; @@ -128,6 +128,7 @@ labels: this.labels, collectionZ: this.collectionZ, groups: this.groups, + frameMeta: this.frameMeta, }; } diff --git a/cvatjs/src/annotations-objects.js b/cvatjs/src/annotations-objects.js index 1d30a0862bde..89c636b593c7 100644 --- a/cvatjs/src/annotations-objects.js +++ b/cvatjs/src/annotations-objects.js @@ -57,6 +57,62 @@ } } + function checkShapeArea(shapeType, points) { + const MIN_SHAPE_LENGTH = 3; + const MIN_SHAPE_AREA = 9; + + if (shapeType === window.cvat.enums.ObjectShape.POINTS) { + return true; + } + + let xmin = Number.MAX_SAFE_INTEGER; + let xmax = Number.MIN_SAFE_INTEGER; + let ymin = Number.MAX_SAFE_INTEGER; + let ymax = Number.MIN_SAFE_INTEGER; + + for (let i = 0; i < points.length - 1; i += 2) { + xmin = Math.min(xmin, points[i]); + xmax = Math.max(xmax, points[i]); + ymin = Math.min(ymin, points[i + 1]); + ymax = Math.max(ymax, points[i + 1]); + } + + if (shapeType === window.cvat.enums.ObjectShape.POLYLINE) { + const length = Math.max( + xmax - xmin, + ymax - ymin, + ); + + return length >= MIN_SHAPE_LENGTH; + } + + const area = (xmax - xmin) * (ymax - ymin); + return area >= MIN_SHAPE_AREA; + } + + function validateAttributeValue(value, attr) { + const { values } = attr; + const type = attr.inputType; + + if (typeof (value) !== 'string') { + throw new window.cvat.exceptions.ArgumentError( + `Attribute value is expected to be string, but got ${typeof (value)}`, + ); + } + + if (type === window.cvat.enums.AttributeType.NUMBER) { + return +value >= +values[0] + && +value <= +values[1] + && !((+value - +values[0]) % +values[2]); + } + + if (type === window.cvat.enums.AttributeType.CHECKBOX) { + return ['true', 'false'].includes(value.toLowerCase()); + } + + return values.includes(value); + } + class Annotation { constructor(data, clientID, injection) { this.taskLabels = injection.labels; @@ -98,6 +154,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, injection); + this.frameMeta = injection.frameMeta; this.collectionZ = injection.collectionZ; const z = this._getZ(this.frame); z.max = Math.max(z.max, this.zOrder || 0); @@ -227,22 +284,47 @@ } if (updated.attributes) { - const labelAttributes = copy.label - .attributes.map(attr => `${attr.id}`); + const labelAttributes = copy.label.attributes + .reduce((accumulator, value) => { + accumulator[value.id] = value; + return accumulator; + }, {}); for (const attrID of Object.keys(data.attributes)) { - if (labelAttributes.includes(attrID)) { - copy.attributes[attrID] = data.attributes[attrID]; + const value = data.attributes[attrID]; + if (attrID in labelAttributes + && validateAttributeValue(value, labelAttributes[attrID])) { + copy.attributes[attrID] = value; + } else { + throw new window.cvat.exceptions.ArgumentError( + `Trying to save unknown attribute with id ${attrID} and value ${value}`, + ); } } } if (updated.points) { checkObjectType('points', data.points, null, Array); - copy.points = []; - for (const coordinate of data.points) { - checkObjectType('coordinate', coordinate, 'number', null); - copy.points.push(coordinate); + checkNumberOfPoints(this.shapeType, data.points); + + // cut points + const { width, height } = this.frameMeta[frame]; + const cutPoints = []; + for (let i = 0; i < data.points.length - 1; i += 2) { + const x = data.points[i]; + const y = data.points[i + 1]; + + checkObjectType('coordinate', x, 'number', null); + checkObjectType('coordinate', y, 'number', null); + + cutPoints.push( + Math.clamp(x, 0, width), + Math.clamp(y, 0, height), + ); + } + + if (checkShapeArea(this.shapeType, cutPoints)) { + copy.points = cutPoints; } } @@ -372,7 +454,6 @@ {}, this.getPosition(frame), { attributes: this.getAttributes(frame), - label: this.label, group: this.group, objectType: window.cvat.enums.ObjectType.TRACK, shapeType: this.shapeType, @@ -386,7 +467,9 @@ this.cache[frame] = interpolation; } - return JSON.parse(JSON.stringify(this.cache[frame])); + const result = JSON.parse(JSON.stringify(this.cache[frame])); + result.label = this.label; + return result; } neighborsFrames(targetFrame) { @@ -440,8 +523,7 @@ } save(frame, data) { - if (this.lock || data.lock) { - this.lock = data.lock; + if (this.lock && data.lock) { return objectStateFactory.call(this, frame, this.get(frame)); } @@ -462,34 +544,50 @@ this.appendDefaultAttributes.call(copy, copy.label); } - if (updated.attributes) { - const labelAttributes = copy.label.attributes - .reduce((accumulator, value) => { - accumulator[value.id] = value; - return accumulator; - }, {}); + const labelAttributes = copy.label.attributes + .reduce((accumulator, value) => { + accumulator[value.id] = value; + return accumulator; + }, {}); + if (updated.attributes) { for (const attrID of Object.keys(data.attributes)) { - if (attrID in labelAttributes) { - copy.attributes[attrID] = data.attributes[attrID]; - if (!labelAttributes[attrID].mutable) { - this.attributes[attrID] = data.attributes[attrID]; - } else { - // Mutable attributes will be updated later - positionUpdated = true; - } + const value = data.attributes[attrID]; + if (attrID in labelAttributes + && validateAttributeValue(value, labelAttributes[attrID])) { + copy.attributes[attrID] = value; + } else { + throw new window.cvat.exceptions.ArgumentError( + `Trying to save unknown attribute with id ${attrID} and value ${value}`, + ); } } } if (updated.points) { checkObjectType('points', data.points, null, Array); - copy.points = []; - for (const coordinate of data.points) { - checkObjectType('coordinate', coordinate, 'number', null); - copy.points.push(coordinate); + checkNumberOfPoints(this.shapeType, data.points); + + // cut points + const { width, height } = this.frameMeta[frame]; + const cutPoints = []; + for (let i = 0; i < data.points.length - 1; i += 2) { + const x = data.points[i]; + const y = data.points[i + 1]; + + checkObjectType('coordinate', x, 'number', null); + checkObjectType('coordinate', y, 'number', null); + + cutPoints.push( + Math.clamp(x, 0, width), + Math.clamp(y, 0, height), + ); + } + + if (checkShapeArea(this.shapeType, cutPoints)) { + copy.points = cutPoints; + positionUpdated = true; } - positionUpdated = true; } if (updated.occluded) { @@ -540,8 +638,18 @@ this.cache[frame][prop] = copy[prop]; } + if (updated.attributes) { + // Mutable attributes will be updated below + for (const attrID of Object.keys(copy.attributes)) { + if (!labelAttributes[attrID].mutable) { + this.shapes[frame].attributes[attrID] = data.attributes[attrID]; + this.shapes[frame].attributes[attrID] = data.attributes[attrID]; + } + } + } + if (updated.label) { - for (const shape of this.shapes) { + for (const shape of Object.values(this.shapes)) { shape.attributes = {}; } } @@ -584,15 +692,9 @@ }; if (updated.attributes) { - const labelAttributes = this.label.attributes - .reduce((accumulator, value) => { - accumulator[value.id] = value; - return accumulator; - }, {}); - // Unmutable attributes were updated above - for (const attrID of Object.keys(data.attributes)) { - if (attrID in labelAttributes && labelAttributes[attrID].mutable) { + for (const attrID of Object.keys(copy.attributes)) { + if (labelAttributes[attrID].mutable) { this.shapes[frame].attributes[attrID] = data.attributes[attrID]; this.shapes[frame].attributes[attrID] = data.attributes[attrID]; } diff --git a/cvatjs/src/api.js b/cvatjs/src/api.js index 4f88de8cea7a..af53cf6f5a14 100644 --- a/cvatjs/src/api.js +++ b/cvatjs/src/api.js @@ -348,14 +348,12 @@ */ config: { /** + * @memberof module:API.cvat.config * @property {string} backendAPI host with a backend api * @memberof module:API.cvat.config * @property {string} proxy Axios proxy settings. * For more details please read here * @memberof module:API.cvat.config - * @property {integer} preloadFrames the number of subsequent frames which are - * loaded in background - * @memberof module:API.cvat.config * @property {integer} taskID this value is displayed in a logs if available * @memberof module:API.cvat.config * @property {integer} jobID this value is displayed in a logs if available @@ -364,7 +362,6 @@ * value which is displayed in a logs * @memberof module:API.cvat.config */ - preloadFrames: 300, backendAPI: 'http://localhost:7000/api/v1', proxy: false, taskID: undefined, @@ -451,5 +448,9 @@ require('browser-env')(); } + Math.clamp = function (value, min, max) { + return Math.min(Math.max(value, min), max); + }; + window.cvat = Object.freeze(implementAPI(cvat)); })(); diff --git a/cvatjs/src/object-state.js b/cvatjs/src/object-state.js index 1188a54fd140..23c406c01f5a 100644 --- a/cvatjs/src/object-state.js +++ b/cvatjs/src/object-state.js @@ -153,6 +153,7 @@ * @name points * @type {number[]} * @memberof module:API.cvat.classes.ObjectState + * @throws {module:API.cvat.exceptions.ArgumentError} * @instance */ get: () => data.points, @@ -162,7 +163,9 @@ data.points = [...points]; } else { throw new window.cvat.exceptions.ArgumentError( - `Points value must be an array, but got ${points.constructor.name}`, + 'Points are expected to be an array ' + + `but got ${typeof (points) === 'object' + ? points.constructor.name : typeof (points)}`, ); } }, @@ -258,14 +261,10 @@ get: () => data.attributes, set: (attributes) => { if (typeof (attributes) !== 'object') { - if (typeof (attributes) === 'undefined') { - throw new window.cvat.exceptions.ArgumentError( - 'Expected attributes are object, but got undefined', - ); - } - throw new window.cvat.exceptions.ArgumentError( - `Expected attributes are object, but got ${attributes.constructor.name}`, + 'Attributes are expected to be an object ' + + `but got ${typeof (attributes) === 'object' + ? attributes.constructor.name : typeof (attributes)}`, ); } @@ -283,11 +282,13 @@ this.outside = serialized.outside; this.keyframe = serialized.keyframe; this.occluded = serialized.occluded; - this.points = serialized.points; this.color = serialized.color; this.lock = serialized.lock; // It can be undefined in a constructor and it can be defined later + if (typeof (serialized.points) !== 'undefined') { + this.points = serialized.points; + } if (typeof (serialized.attributes) !== 'undefined') { this.attributes = serialized.attributes; } diff --git a/cvatjs/tests/api/annotations.js b/cvatjs/tests/api/annotations.js index ab0ede5f1ecf..6d6f3174900a 100644 --- a/cvatjs/tests/api/annotations.js +++ b/cvatjs/tests/api/annotations.js @@ -192,6 +192,10 @@ describe('Feature: put annotations', () => { label: task.labels[0], }); + await expect(task.annotations.put([state])) + .rejects.toThrow(window.cvat.exceptions.DataError); + + delete state.points; await expect(task.annotations.put([state])) .rejects.toThrow(window.cvat.exceptions.DataError); diff --git a/cvatjs/tests/api/object-state.js b/cvatjs/tests/api/object-state.js index 5963ccd4ab1d..6c006e62812f 100644 --- a/cvatjs/tests/api/object-state.js +++ b/cvatjs/tests/api/object-state.js @@ -18,20 +18,255 @@ jest.mock('../../src/server-proxy', () => { // Initialize api require('../../src/api'); -describe('Feature: set attributes for object state', () => { - test('get meta for a job', async () => { +describe('Feature: set attributes for an object state', () => { + test('set a valid value', () => { + const state = new window.cvat.classes.ObjectState({ + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + frame: 5, + }); + const attributes = { + 5: 'man', + 6: 'glasses', + }; + + state.attributes = attributes; + expect(state.attributes).toEqual(attributes); + }); + + test('trying to set a bad value', () => { + const state = new window.cvat.classes.ObjectState({ + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + frame: 5, + }); + + let attributes = 'bad attribute'; + expect(() => { + state.attributes = attributes; + }).toThrow(window.cvat.exceptions.ArgumentError); + + attributes = 5; + expect(() => { + state.attributes = attributes; + }).toThrow(window.cvat.exceptions.ArgumentError); + + attributes = false; + expect(() => { + state.attributes = attributes; + }).toThrow(window.cvat.exceptions.ArgumentError); }); }); -describe('Feature: set points for object state', () => { - test('get meta for a task', async () => { +describe('Feature: set points for an object state', () => { + test('set a valid value', () => { + const state = new window.cvat.classes.ObjectState({ + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + frame: 5, + }); + const points = [1, 2, 3, 4]; + state.points = points; + expect(state.points).toEqual(points); + }); + + test('trying to set a bad value', () => { + const state = new window.cvat.classes.ObjectState({ + objectType: window.cvat.enums.ObjectType.SHAPE, + shapeType: window.cvat.enums.ObjectShape.RECTANGLE, + frame: 5, + }); + + let points = 'bad points'; + expect(() => { + state.points = points; + }).toThrow(window.cvat.exceptions.ArgumentError); + + points = 5; + expect(() => { + state.points = points; + }).toThrow(window.cvat.exceptions.ArgumentError); + + points = false; + expect(() => { + state.points = points; + }).toThrow(window.cvat.exceptions.ArgumentError); + + points = {}; + expect(() => { + state.points = points; + }).toThrow(window.cvat.exceptions.ArgumentError); }); }); -describe('Feature: save object state', () => { - test('get meta for a task', async () => { +describe('Feature: save object from its state', () => { + test('save valid values for a shape', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + let state = annotations[0]; + expect(state.objectType).toBe(window.cvat.enums.ObjectType.SHAPE); + expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + state.points = [0, 0, 100, 100]; + state.occluded = true; + [, state.label] = task.labels; + state.lock = true; + state = await state.save(); + expect(state).toBeInstanceOf(window.cvat.classes.ObjectState); + expect(state.label.id).toBe(task.labels[1].id); + expect(state.lock).toBe(true); + expect(state.occluded).toBe(true); + expect(state.points).toEqual([0, 0, 100, 100]); + }); + + test('save valid values for a track', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(10); + let state = annotations[1]; + expect(state.objectType).toBe(window.cvat.enums.ObjectType.TRACK); + expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE); + + state.occluded = true; + state.lock = true; + state.points = [100, 200, 200, 400]; + state.attributes = { + 1: 'sitting', + 3: 'female', + 2: '10', + 4: 'true', + }; + + state = await state.save(); + expect(state).toBeInstanceOf(window.cvat.classes.ObjectState); + expect(state.lock).toBe(true); + expect(state.occluded).toBe(true); + expect(state.points).toEqual([100, 200, 200, 400]); + expect(state.attributes[1]).toBe('sitting'); + expect(state.attributes[2]).toBe('10'); + expect(state.attributes[3]).toBe('female'); + expect(state.attributes[4]).toBe('true'); + + state.lock = false; + [state.label] = task.labels; + state = await state.save(); + expect(state.label.id).toBe(task.labels[0].id); + + state.outside = true; + state = await state.save(); + expect(state.lock).toBe(false); + expect(state.outside).toBe(true); + + state.keyframe = false; + state = await state.save(); + expect(state.keyframe).toBe(false); + }); + + test('save bad values for a shape', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + + state.occluded = 'false'; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + const oldPoints = state.points; + state.occluded = false; + state.points = ['100', '50', '100', {}]; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.points = oldPoints; + state.lock = 'true'; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + const oldLabel = state.label; + state.lock = false; + state.label = 1; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.label = oldLabel; + state.attributes = { 1: {}, 2: false, 3: () => {} }; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + }); + + test('save bad values for a track', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('trying to change locked shape', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('trying to set too small area of a shape', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('trying to set too small area of a track', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('trying to set too small length of a shape', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('trying to set too small length of a track', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); +}); + +describe('Feature: delete object', () => { + test('delete a shape', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('delete a track', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); +}); + +describe('Feature: change z order of an object', () => { + test('up z order for a shape', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('up z order for a track', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + + test('down z order for a shape', async () => { + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; + }); + test('down z order for a track', async () => { + const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const annotations = await task.annotations.get(0); + const state = annotations[0]; }); }); From 688ec537a2e406e70f5c447b208dd699aaef9a7d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 19 Jul 2019 14:56:45 +0300 Subject: [PATCH 15/16] Object state tests --- cvatjs/src/annotations-objects.js | 12 +++- cvatjs/tests/api/object-state.js | 103 ++++++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 17 deletions(-) diff --git a/cvatjs/src/annotations-objects.js b/cvatjs/src/annotations-objects.js index 89c636b593c7..71fa42cfd19f 100644 --- a/cvatjs/src/annotations-objects.js +++ b/cvatjs/src/annotations-objects.js @@ -156,9 +156,6 @@ this.frameMeta = injection.frameMeta; this.collectionZ = injection.collectionZ; - const z = this._getZ(this.frame); - z.max = Math.max(z.max, this.zOrder || 0); - z.min = Math.min(z.min, this.zOrder || 0); this.color = color; this.shapeType = null; @@ -212,6 +209,10 @@ this.points = data.points; this.occluded = data.occluded; this.zOrder = data.z_order; + + const z = this._getZ(this.frame); + z.max = Math.max(z.max, this.zOrder || 0); + z.min = Math.min(z.min, this.zOrder || 0); } // Method is used to export data to the server @@ -629,6 +630,11 @@ copy.color = data.color; } + if (updated.keyframe) { + // Just check here + checkObjectType('keyframe', data.keyframe, 'boolean', null); + } + // Commit all changes for (const prop of Object.keys(copy)) { if (prop in this) { diff --git a/cvatjs/tests/api/object-state.js b/cvatjs/tests/api/object-state.js index 6c006e62812f..d1dddd8308b9 100644 --- a/cvatjs/tests/api/object-state.js +++ b/cvatjs/tests/api/object-state.js @@ -198,50 +198,109 @@ describe('Feature: save object from its state', () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); const state = annotations[0]; + + state.occluded = 'false'; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + const oldPoints = state.points; + state.occluded = false; + state.points = ['100', '50', '100', {}]; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.points = oldPoints; + state.lock = 'true'; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + const oldLabel = state.label; + state.lock = false; + state.label = 1; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.label = oldLabel; + state.outside = 5; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.outside = false; + state.keyframe = '10'; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); + + state.keyframe = true; + state.attributes = { 1: {}, 2: false, 3: () => {} }; + await expect(state.save()) + .rejects.toThrow(window.cvat.exceptions.ArgumentError); }); test('trying to change locked shape', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); - const state = annotations[0]; + let state = annotations[0]; + + state.lock = true; + state = await state.save(); + + const { points } = state; + state.points = [0, 0, 500, 500]; + state = await state.save(); + expect(state.points).toEqual(points); }); test('trying to set too small area of a shape', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); - const state = annotations[0]; + let state = annotations[0]; + + const { points } = state; + state.points = [0, 0, 2, 2]; // area is 4 + state = await state.save(); + expect(state.points).toEqual(points); }); test('trying to set too small area of a track', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); - const state = annotations[0]; + let state = annotations[0]; + + const { points } = state; + state.points = [0, 0, 2, 2]; // area is 4 + state = await state.save(); + expect(state.points).toEqual(points); }); test('trying to set too small length of a shape', async () => { - const task = (await window.cvat.tasks.get({ id: 101 }))[0]; + const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotations = await task.annotations.get(0); - const state = annotations[0]; - }); + let state = annotations[8]; - test('trying to set too small length of a track', async () => { - const task = (await window.cvat.tasks.get({ id: 101 }))[0]; - const annotations = await task.annotations.get(0); - const state = annotations[0]; + const { points } = state; + state.points = [0, 0, 2, 2]; // length is 2 + state = await state.save(); + expect(state.points).toEqual(points); }); }); describe('Feature: delete object', () => { test('delete a shape', async () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; - const annotations = await task.annotations.get(0); - const state = annotations[0]; + const annotationsBefore = await task.annotations.get(0); + const { length } = annotationsBefore; + await annotationsBefore[0].delete(); + const annotationsAfter = await task.annotations.get(0); + expect(annotationsAfter).toHaveLength(length - 1); }); test('delete a track', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; - const annotations = await task.annotations.get(0); - const state = annotations[0]; + const annotationsBefore = await task.annotations.get(0); + const { length } = annotationsBefore; + await annotationsBefore[0].delete(); + const annotationsAfter = await task.annotations.get(0); + expect(annotationsAfter).toHaveLength(length - 1); }); }); @@ -250,23 +309,39 @@ describe('Feature: change z order of an object', () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotations = await task.annotations.get(0); const state = annotations[0]; + + const { zOrder } = state; + await state.up(); + expect(state.zOrder).toBeGreaterThan(zOrder); }); test('up z order for a track', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); const state = annotations[0]; + + const { zOrder } = state; + await state.up(); + expect(state.zOrder).toBeGreaterThan(zOrder); }); test('down z order for a shape', async () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotations = await task.annotations.get(0); const state = annotations[0]; + + const { zOrder } = state; + await state.down(); + expect(state.zOrder).toBeLessThan(zOrder); }); test('down z order for a track', async () => { const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const annotations = await task.annotations.get(0); const state = annotations[0]; + + const { zOrder } = state; + await state.down(); + expect(state.zOrder).toBeLessThan(zOrder); }); }); From e56259824555b8743e34034883998c73c2a654e7 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 19 Jul 2019 17:17:22 +0300 Subject: [PATCH 16/16] Renamed method FrameData.frame() => FrameData.data() --- cvatjs/src/frames.js | 10 +++++----- cvatjs/src/server-proxy.js | 4 ++-- cvatjs/tests/api/frames.js | 8 ++++---- cvatjs/tests/mocks/server-proxy.mock.js | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cvatjs/src/frames.js b/cvatjs/src/frames.js index e636eef0945f..5381fa8a218d 100644 --- a/cvatjs/src/frames.js +++ b/cvatjs/src/frames.js @@ -59,7 +59,7 @@ /** * Method returns URL encoded image which can be placed in the img tag - * @method frame + * @method data * @returns {string} * @memberof module:API.cvat.classes.FrameData * @instance @@ -67,16 +67,16 @@ * @throws {module:API.cvat.exception.ServerError} * @throws {module:API.cvat.exception.PluginError} */ - async frame() { + async data() { const result = await PluginRegistry - .apiWrapper.call(this, FrameData.prototype.frame); + .apiWrapper.call(this, FrameData.prototype.data); return result; } } - FrameData.prototype.frame.implementation = async function () { + FrameData.prototype.data.implementation = async function () { if (!(this.number in frameCache[this.tid])) { - const frame = await serverProxy.frames.getFrame(this.tid, this.number); + const frame = await serverProxy.frames.getData(this.tid, this.number); if (window.URL.createObjectURL) { // browser env const url = window.URL.createObjectURL(new Blob([frame])); diff --git a/cvatjs/src/server-proxy.js b/cvatjs/src/server-proxy.js index a574c434b8f6..086db356ed57 100644 --- a/cvatjs/src/server-proxy.js +++ b/cvatjs/src/server-proxy.js @@ -421,7 +421,7 @@ return response.data; } - async function getFrame(tid, frame) { + async function getData(tid, frame) { const { backendAPI } = window.cvat.config; let response = null; @@ -629,7 +629,7 @@ frames: { value: Object.freeze({ - getFrame, + getData, getMeta, }), writable: false, diff --git a/cvatjs/tests/api/frames.js b/cvatjs/tests/api/frames.js index b944ea23d2dd..1fb76d18af36 100644 --- a/cvatjs/tests/api/frames.js +++ b/cvatjs/tests/api/frames.js @@ -55,17 +55,17 @@ describe('Feature: get frame meta', () => { }); describe('Feature: get frame data', () => { - test('get meta for a task', async () => { + test('get frame data for a task', async () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const frame = await task.frames.get(0); - const frameData = await frame.frame(); + const frameData = await frame.data(); expect(typeof (frameData)).toBe('string'); }); - test('get meta for a job', async () => { + test('get frame data for a job', async () => { const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const frame = await job.frames.get(0); - const frameData = await frame.frame(); + const frameData = await frame.data(); expect(typeof (frameData)).toBe('string'); }); }); diff --git a/cvatjs/tests/mocks/server-proxy.mock.js b/cvatjs/tests/mocks/server-proxy.mock.js index 6785e9a2615e..e18a3db19c07 100644 --- a/cvatjs/tests/mocks/server-proxy.mock.js +++ b/cvatjs/tests/mocks/server-proxy.mock.js @@ -188,7 +188,7 @@ class ServerProxy { return JSON.parse(JSON.stringify(usersDummyData)).results[0]; } - async function getFrame() { + async function getData() { return 'DUMMY_IMAGE'; } @@ -275,7 +275,7 @@ class ServerProxy { frames: { value: Object.freeze({ - getFrame, + getData, getMeta, }), writable: false,