diff --git a/.dockerignore b/.dockerignore index 19b61d36..fbc8a261 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,8 @@ +# front +front/node_modules/ +front/webpack-stats.json +front/dist/ + .git/ -.venv/ -node_modules/ setup/ +__pycache__ diff --git a/Dockerfile b/Dockerfile index 1857b74f..afc61a0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7-slim +FROM python:3.8.1-slim SHELL ["/bin/bash", "-c"] ENV DEBIAN_FRONTEND=noninteractive @@ -7,8 +7,8 @@ ENV LANG "C.UTF-8" ENV APP_PATH /opt/automan # install python -RUN apt update && \ - apt install -y --no-install-recommends \ +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ apt-transport-https \ default-libmysqlclient-dev \ ca-certificates \ @@ -30,27 +30,32 @@ RUN apt update && \ wget \ xz-utils \ zlib1g-dev && \ - apt clean && \ - rm -rf /var/lib/apt/lists/* && \ pip install --no-cache-dir pipenv && \ curl -sL https://deb.nodesource.com/setup_10.x | bash - && \ - apt install -y nodejs && \ + apt-get install -y nodejs && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ - apt update && apt install -y yarn + apt-get update && \ + apt-get install -y yarn && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* -# FIXME COPY -> RUN git clone -# cd automan -# docker build -t automan-labeling-app -f Dockerfile . +# setup pipenv COPY automan/Pipfile* /tmp/automan/ WORKDIR /tmp/automan RUN pipenv install --system --deploy -COPY . $APP_PATH/ -# setup frontend environment +# setup yarn packages +COPY front/package.json $APP_PATH/front/ WORKDIR $APP_PATH/front -RUN yarn install && yarn build +RUN yarn install + +# setup frontend environment +COPY front/ $APP_PATH/front/ +RUN yarn build +COPY automan/ $APP_PATH/automan/ +COPY bin/ $APP_PATH/bin/ WORKDIR $APP_PATH/ ENTRYPOINT ["./bin/docker-entrypoint.sh"] CMD ["uwsgi", "--ini", "conf/app.ini"] diff --git a/automan/Pipfile b/automan/Pipfile index da74ede3..d6d650eb 100644 --- a/automan/Pipfile +++ b/automan/Pipfile @@ -8,18 +8,16 @@ pep8 = "*" flake8 = "*" [packages] -djangorestframework = "==3.9.1" +djangorestframework = "==3.11.0" djangorestframework-jwt = "==1.11.0" mysql-connector = "==2.1.7" python-dotenv = "==0.9.1" django-webpack-loader = "==0.6.0" mysqlclient = "==1.3.13" requests = "==2.20.0" -rospkg = "==1.1.7" azure = "==4.0.0" boto3 = "==1.9.45" -Django = ">=2.1.9" -PyYAML = ">=4.2b1" +Django = ">=3.0.0" uWSGI = "==2.0.17.1" Cerberus = "==1.2" kubernetes = "*" diff --git a/automan/Pipfile.lock b/automan/Pipfile.lock index c3aa3d8d..912b62c6 100644 --- a/automan/Pipfile.lock +++ b/automan/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ebc26391b10c847194edf144a0dc28b5f208c08e6a1d707e6198a757b8a36057" + "sha256": "036ac789762da22e53030b035baf4fddbaac77f1737d1351fa9a464d1ac16fb1" }, "pipfile-spec": 6, "requires": { @@ -18,17 +18,17 @@ "default": { "adal": { "hashes": [ - "sha256:82e84fa0b442caf8131f1e87a7ebee2546f57ab16a8917a599a02b6e455cb1b0", - "sha256:b6edd095be66561382bdaa59d40b04490e93149fb3b7fa44c1fa5504eed5b8b9" + "sha256:5a7f1e037c6290c6d7609cab33a9e5e988c2fbec5c51d1c4c649ee3faff37eaf", + "sha256:fd17e5661f60634ddf96a569b95d34ccb8a98de60593d729c28bdcfe360eaad1" ], - "version": "==1.2.1" + "version": "==1.2.2" }, - "asn1crypto": { + "asgiref": { "hashes": [ - "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", - "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", + "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" ], - "version": "==0.24.0" + "version": "==3.2.3" }, "azure": { "hashes": [ @@ -54,10 +54,10 @@ }, "azure-common": { "hashes": [ - "sha256:c068d7379d6dc2f4093209cb7c3c1447e85d398374c628003aea9540d710ce4f", - "sha256:c8e4a7bf15f139f779a415d2d3c371738b1e9f5e14abd9c18af6b9bed3babf35" + "sha256:184ad6a05a3089dfdc1ce07c1cbfa489bbc45b5f6f56e848cac0851e6443da21", + "sha256:3d64e9ab995300f42abd5bc0ef02f02bab661321e394d4dbacb4382ea1fb2f72" ], - "version": "==1.1.22" + "version": "==1.1.24" }, "azure-cosmosdb-nspkg": { "hashes": [ @@ -68,17 +68,17 @@ }, "azure-cosmosdb-table": { "hashes": [ - "sha256:4a34c2c792036afc2a3811f4440ab967351e9ceee6542cc96453b63c678c0145", - "sha256:a9c3d2a75b376a45f4bda84af28e698b7578544bb0e9eb7b53fedef3369634a0" + "sha256:5f061d2ab8dcf2f0b4e965d5976e7b7aeb1247ea896911f0e1d29092aaaa29c7", + "sha256:ee525233d6c8c016526593bf28f8a374275cfe204a00c41134b83a1736f7b5f7" ], - "version": "==1.0.5" + "version": "==1.0.6" }, "azure-datalake-store": { "hashes": [ - "sha256:6d719302f2fcac6c941c47d934a6f20f71ec7a519ab53f4aac9283b1db9d5ccc", - "sha256:b71226f87b5c1ee6c8e895757ac501dcc0a6ec519a901201080f8221dba9657d" + "sha256:b35108939f9ac4b6bc568e9b735e3e38a5fdabe00065073b5e48659929d536d1", + "sha256:d27c335783d4add00b3a5f709341e4a8009857440209e15a739a9a96b52386f7" ], - "version": "==0.0.45" + "version": "==0.0.48" }, "azure-eventgrid": { "hashes": [ @@ -618,24 +618,17 @@ }, "botocore": { "hashes": [ - "sha256:13e75f594c77988efd13f0862f3c7397d587e74b623fe0825d48b0ec0dc96d6b", - "sha256:1fa1ad4be9e9fea77eed7d5021be4283e9bcfecc60d2de83f96552025c4725d1" + "sha256:3baf129118575602ada9926f5166d82d02273c250d0feb313fc270944b27c48b", + "sha256:dc080aed4f9b220a9e916ca29ca97a9d37e8e1d296fe89cbaeef929bf0c8066b" ], - "version": "==1.12.172" + "version": "==1.12.253" }, "cachetools": { "hashes": [ - "sha256:428266a1c0d36dc5aca63a2d7c5942e88c2c898d72139fca0e97fdd2380517ae", - "sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a" + "sha256:9a52dd97a85f257f4e4127f15818e71a0c7899f121b34591fcc1173ea79a0198", + "sha256:b304586d357c43221856be51d73387f93e2a961598a9b6b6670664746f3b6c6c" ], - "version": "==3.1.1" - }, - "catkin-pkg": { - "hashes": [ - "sha256:09653215b00e525c9e12f6aba57c85297138bd4eb51f17edcd2d011f2f6dd74a", - "sha256:77fdfdcf79b1b92498c83eede9ef727e9c6eeec5b0a5e49c0ce83b8caa0afb36" - ], - "version": "==0.4.12" + "version": "==4.0.0" }, "cerberus": { "hashes": [ @@ -646,43 +639,48 @@ }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.6.16" + "version": "==2019.11.28" }, "cffi": { "hashes": [ - "sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", - "sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", - "sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", - "sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", - "sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", - "sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", - "sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", - "sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", - "sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", - "sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", - "sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", - "sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", - "sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", - "sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", - "sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", - "sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", - "sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", - "sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", - "sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", - "sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", - "sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", - "sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", - "sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", - "sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", - "sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", - "sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", - "sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", - "sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201" - ], - "version": "==1.12.3" + "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", + "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", + "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", + "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", + "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", + "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", + "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", + "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", + "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", + "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", + "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", + "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", + "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", + "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", + "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", + "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", + "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", + "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", + "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", + "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", + "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", + "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", + "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", + "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", + "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", + "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", + "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", + "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", + "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", + "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", + "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", + "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", + "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" + ], + "version": "==1.13.2" }, "chardet": { "hashes": [ @@ -707,32 +705,37 @@ }, "cryptography": { "hashes": [ - "sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c", - "sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643", - "sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216", - "sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799", - "sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a", - "sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9", - "sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc", - "sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8", - "sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53", - "sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1", - "sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609", - "sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292", - "sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e", - "sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6", - "sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed", - "sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d" - ], - "version": "==2.7" + "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c", + "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595", + "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad", + "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651", + "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2", + "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff", + "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d", + "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42", + "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d", + "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e", + "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912", + "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793", + "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13", + "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7", + "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0", + "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879", + "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f", + "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9", + "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2", + "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf", + "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8" + ], + "version": "==2.8" }, "django": { "hashes": [ - "sha256:753d30d3eb078064d2ddadfea65083c9848074a7f93d7b4dc7fa6b1380d278f5", - "sha256:7cb67e8b934fab23b6daed7144da52e8a25a47eba7f360ca43d2b448506b01ad" + "sha256:4f2c913303be4f874015993420bf0bd8fd2097a9c88e6b49c6a92f9bdd3fb13a", + "sha256:8c3575f81e11390893860d97e1e0154c47512f180ea55bd84ce8fa69ba8051ca" ], "index": "pypi", - "version": "==2.2.2" + "version": "==3.0.2" }, "django-rest-swagger": { "hashes": [ @@ -752,11 +755,11 @@ }, "djangorestframework": { "hashes": [ - "sha256:79c6efbb2514bc50cf25906d7c0a5cfead714c7af667ff4bd110312cd380ae66", - "sha256:a4138613b67e3a223be6c97f53b13d759c5b90d2b433bad670b8ebf95402075f" + "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4", + "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f" ], "index": "pypi", - "version": "==3.9.1" + "version": "==3.11.0" }, "djangorestframework-jwt": { "hashes": [ @@ -768,18 +771,18 @@ }, "docutils": { "hashes": [ - "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", - "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", + "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", + "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" ], - "version": "==0.14" + "version": "==0.15.2" }, "google-auth": { "hashes": [ - "sha256:0f7c6a64927d34c1a474da92cfc59e552a5d3b940d3266606c6a28b72888b9e4", - "sha256:20705f6803fd2c4d1cc2dcb0df09d4dfcb9a7d51fd59e94a3a28231fd93119ed" + "sha256:44549e69ac39acf41fdf47f3f39a06e4e68378476806760d94a2c6a361b2bb06", + "sha256:b2d83edc02a9deeed9b1b839046671fd9eda223d21bd2dd50051559787032fd8" ], - "version": "==1.6.3" + "version": "==1.11.0" }, "idna": { "hashes": [ @@ -803,10 +806,10 @@ }, "jinja2": { "hashes": [ - "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", - "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" ], - "version": "==2.10.1" + "version": "==2.11.1" }, "jmespath": { "hashes": [ @@ -817,11 +820,11 @@ }, "kubernetes": { "hashes": [ - "sha256:a8b0aed55ba946faea660712595a52ae53a8854df773d96f47a63fa0c9d4e3bf", - "sha256:f56137a298cb1453dd908b49dd4169347287c971e8cabd11b32f27570fec314c" + "sha256:3770a496663396ad1def665eeadb947b3f45217a08b64b10c01a57e981ac8592", + "sha256:a6dee02a1b39ea4bb9c4c2cc415ea0ada33d8ea0a920f7d4fb6d166989dcac01" ], "index": "pypi", - "version": "==9.0.0" + "version": "==10.0.1" }, "markupsafe": { "hashes": [ @@ -829,13 +832,16 @@ "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", @@ -852,23 +858,25 @@ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "version": "==1.1.1" }, "msrest": { "hashes": [ - "sha256:05538c68251eb0c81bd2010524d8ff36d4266ec0669338fbdcecfd23c733231c", - "sha256:8143093308975f815f968b0d2a1ac8e26ba217eb6d03f3f27aac616aa3a25bd0" + "sha256:40faff88e151d393e29512e58b27d141974d6a963e63e4a340fc0ceb13c15f37", + "sha256:57eba26bd09d839d8f9133aea4b632d3216902efedf580b1a757c67b6538fb2c" ], - "version": "==0.6.7" + "version": "==0.6.11" }, "msrestazure": { "hashes": [ - "sha256:070220fa9c86b55026360435b655d1d67ff4306fd1412687c400dc549e9647b7", - "sha256:e9d525b11d88f1073744e128ae19a4e023e4085893cfcfd02483fdd4cee25091" + "sha256:63db9f646fffc9244b332090e679d1e5f283ac491ee0cc321f5116f9450deb4a", + "sha256:fecb6a72a3eb5483e4deff38210d26ae42d3f6d488a7a275bd2423a1a014b22c" ], - "version": "==0.6.1" + "version": "==0.6.2" }, "mysql-connector": { "hashes": [ @@ -886,39 +894,37 @@ }, "numpy": { "hashes": [ - "sha256:0778076e764e146d3078b17c24c4d89e0ecd4ac5401beff8e1c87879043a0633", - "sha256:141c7102f20abe6cf0d54c4ced8d565b86df4d3077ba2343b61a6db996cefec7", - "sha256:14270a1ee8917d11e7753fb54fc7ffd1934f4d529235beec0b275e2ccf00333b", - "sha256:27e11c7a8ec9d5838bc59f809bfa86efc8a4fd02e58960fa9c49d998e14332d5", - "sha256:2a04dda79606f3d2f760384c38ccd3d5b9bb79d4c8126b67aff5eb09a253763e", - "sha256:3c26010c1b51e1224a3ca6b8df807de6e95128b0908c7e34f190e7775455b0ca", - "sha256:52c40f1a4262c896420c6ea1c6fda62cf67070e3947e3307f5562bd783a90336", - "sha256:6e4f8d9e8aa79321657079b9ac03f3cf3fd067bf31c1cca4f56d49543f4356a5", - "sha256:7242be12a58fec245ee9734e625964b97cf7e3f2f7d016603f9e56660ce479c7", - "sha256:7dc253b542bfd4b4eb88d9dbae4ca079e7bf2e2afd819ee18891a43db66c60c7", - "sha256:94f5bd885f67bbb25c82d80184abbf7ce4f6c3c3a41fbaa4182f034bba803e69", - "sha256:a89e188daa119ffa0d03ce5123dee3f8ffd5115c896c2a9d4f0dbb3d8b95bfa3", - "sha256:ad3399da9b0ca36e2f24de72f67ab2854a62e623274607e37e0ce5f5d5fa9166", - "sha256:b0348be89275fd1d4c44ffa39530c41a21062f52299b1e3ee7d1c61f060044b8", - "sha256:b5554368e4ede1856121b0dfa35ce71768102e4aa55e526cb8de7f374ff78722", - "sha256:cbddc56b2502d3f87fda4f98d948eb5b11f36ff3902e17cb6cc44727f2200525", - "sha256:d79f18f41751725c56eceab2a886f021d70fd70a6188fd386e29a045945ffc10", - "sha256:dc2ca26a19ab32dc475dbad9dfe723d3a64c835f4c23f625c2b6566ca32b9f29", - "sha256:dd9bcd4f294eb0633bb33d1a74febdd2b9018b8b8ed325f861fffcd2c7660bb8", - "sha256:e8baab1bc7c9152715844f1faca6744f2416929de10d7639ed49555a85549f52", - "sha256:ec31fe12668af687b99acf1567399632a7c47b0e17cfb9ae47c098644ef36797", - "sha256:f12b4f7e2d8f9da3141564e6737d79016fe5336cc92de6814eba579744f65b0a", - "sha256:f58ac38d5ca045a377b3b377c84df8175ab992c970a53332fa8ac2373df44ff7" + "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", + "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", + "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", + "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", + "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", + "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", + "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", + "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", + "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", + "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", + "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", + "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", + "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", + "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", + "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", + "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", + "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", + "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", + "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", + "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", + "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5" ], "index": "pypi", - "version": "==1.16.4" + "version": "==1.18.1" }, "oauthlib": { "hashes": [ - "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", - "sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e" + "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", + "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" ], - "version": "==3.0.1" + "version": "==3.1.0" }, "openapi-codec": { "hashes": [ @@ -928,50 +934,50 @@ }, "opencv-python": { "hashes": [ - "sha256:1703a296a96d3d46615e5053f224867977accb4240bcaa0fcabcb0768bf5ac13", - "sha256:1777ce7535ee7a1995cae168a107a1320e9df13648b930e72a1a2c2eccd64cda", - "sha256:1e5520482fb18fbd64d079e7f17ac0018f195fd75f6360a53bb82d7903106b50", - "sha256:25522dcf2529614750a71112a6659759080b4bdc2323f19d47f4d895960fd796", - "sha256:2af5f2842ad44c65ae2647377e0ff198719e1a1cfc9c6a19bc0c525c035d4bd8", - "sha256:31ec48d7eca13fc25c287dea7cecab453976e372cad8f50d55c054a247efda21", - "sha256:47cf48ff5dbd554e9f58cc9e98cf0b5de3f6a971172612bffa06bc5fb79ce872", - "sha256:494f98366bb5d6c2ac7e50e6617139f353704fd97a6d12ec9d392e72817d5cb0", - "sha256:4a9845870739e640e3350a8d98d511c92c087fe3d66090e83be7bf94e0ac64f7", - "sha256:4ac29cc0847d948a6636899014e84e165c30cc8779d6218394d44363462a01ce", - "sha256:5857ace03b7854221abf8072462d306c2c2ce4e366190b21d90ee8ee8aaf5bb4", - "sha256:5b4a23d99d5a2874767034466f5a8fd37b9f93ac14955a01b1a208983c76b9ad", - "sha256:734d87a5021c037064beb62133e135e66c7128e401a63b8b842b809ae2093749", - "sha256:78005c1c5d15ef4e32e0f485557bd15b5b6d87f49c19db7fe3e9246a61ebe7e4", - "sha256:81ae2283225c5c52fc3d72debd4241c30ccff2bb922578bf7867f9851cce3acb", - "sha256:88dbf900f297fdae0f62b899d6a784d8868ec2135854c5f8a9abbad00a6f0c5b", - "sha256:8c98ea7b8d327a31cd6028782a06147d0e0329ae8e829e881fb5d02f7ed8aec9", - "sha256:937d4686fef6967921145290f5b50c01c00c5b5d3542a6519e8a85cd88448723", - "sha256:a057958c0e362b3c4f03b9af1cbdb6d5af035fd22ecd7fd794eba8fdeb049eb8", - "sha256:c41eab31fa2c641226c6187caa391a688d064c99f078d604574f1912296b771f", - "sha256:cf4f7e62d1f80d1fa85a1693a3500def5cde54b2b75212b3609e552e4c25acfb", - "sha256:d90d60143e18334330c149f293071c9f2f3c79c896f33dc4ec65099e58baaaa7", - "sha256:db3106b7ca86999a7bd1f2fcc93e49314e5e6e451356774e421a69428df5020b", - "sha256:dbaf264db56f4771dfac6624f438bc4dc670aa94f61a6138848fcab7e9e77380", - "sha256:e65206c4cf651dc9cf0829962fae8bec986767c9f123d6a1ad17f9356bf7257e", - "sha256:eac94ddc78c58e891cff7180274317dad2938a4ddfc6ced1c04846c7f50e77e9", - "sha256:f2e828711f044a965509c862b3a59b3181e9c56c145a950cb53d43fec54e66d2" + "sha256:0f2e739c582e8c5e432130648bc6d66a56bc65f4cd9ff0bc7033033d2130c7a3", + "sha256:0f3d159ad6cb9cbd188c726f87485f0799a067a0a15f34c25d7b5c8db3cb2e50", + "sha256:167a6aff9bd124a3a67e0ec25d0da5ecdc8d96a56405e3e5e7d586c4105eb1bb", + "sha256:1b90d50bc7a31e9573a8da1b80fcd1e4d9c86c0e5f76387858e1b87eb8b0332b", + "sha256:2baf1213ae2fd678991f905d7b2b94eddfdfb5f75757db0f0b31eebd48ca200d", + "sha256:312dda54c7e809c20d7409418060ae0e9cdbe82975e7ced429eb3c234ffc0d4a", + "sha256:32384e675f7cefe707cac40a95eeb142d6869065e39c5500374116297cd8ca6d", + "sha256:5c50634dd8f2f866fd99fd939292ce10e52bef82804ebc4e7f915221c3b7e951", + "sha256:6841bb9cc24751dbdf94e7eefc4e6d70ec297952501954471299fd12ab67391c", + "sha256:68c1c846dd267cd7e293d3fc0bb238db0a744aa1f2e721e327598f00cb982098", + "sha256:703910aaa1dcd25a412f78a190fb7a352d9a64ee7d9a35566d786f3cc66ebf20", + "sha256:8002959146ed21959e3118c60c8e94ceac02eea15b691da6c62cff4787c63f7f", + "sha256:889eef049d38488b5b4646c48a831feed37c0fd44f3d83c05cff80f4baded145", + "sha256:8c76983c9ec3e4cf3a4c1d172ec4285332d9fb1c7194d724aff0c518437471ee", + "sha256:9cd9bd72f4a9743ef6f11f0f96784bd215a542e996db1717d4c2d3d03eb81a1b", + "sha256:a1a5517301dc8d56243a14253d231ec755b94486b4fff2ae68269bc941bb1f2e", + "sha256:a2b08aec2eacae868723136383d9eb84a33062a7a7ec5ec3bd2c423bd1355946", + "sha256:a8529a79233f3581a66984acd16bce52ab0163f6f77568dd69e9ee4956d2e1db", + "sha256:afbc81a3870739610a9f9a1197374d6a45892cf1933c90fc5617d39790991ed3", + "sha256:baeb5dd8b21c718580687f5b4efd03f8139b1c56239cdf6b9805c6946e80f268", + "sha256:db1d49b753e6e6c76585f21d09c7e9812176732baa9bddb64bc2fc6cd24d4179", + "sha256:e242ed419aeb2488e0f9ee6410a34917f0f8d62b3ae96aa3170d83bae75004e2", + "sha256:e36a8857be2c849e54009f1bee25e8c34fbc683fcd38c6c700af4cba5f8d57c2", + "sha256:e699232fd033ef0053efec2cba0a7505514f374ba7b18c732a77cb5304311ef9", + "sha256:eae3da9231d87980f8082d181c276a04f7a6fdac130cebd467390b96dd05f944", + "sha256:ee6814c94dbf1cae569302afef9dd29efafc52373e8770ded0db549a3b6e0c00", + "sha256:f01a87a015227d8af407161eb48222fc3c8b01661cdc841e2b86eee4f1a7a417" ], "index": "pypi", - "version": "==4.1.0.25" + "version": "==4.2.0.32" }, "pyasn1": { "hashes": [ - "sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7", - "sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e" + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" ], - "version": "==0.4.5" + "version": "==0.4.8" }, "pyasn1-modules": { "hashes": [ - "sha256:ef721f68f7951fab9b0404d42590f479e30d9005daccb1699b0a51bb4177db96", - "sha256:f309b6c94724aeaf7ca583feb1cc70430e10d7551de5e36edfc1ae6909bcfb3c" + "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", + "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74" ], - "version": "==0.2.5" + "version": "==0.2.8" }, "pycparser": { "hashes": [ @@ -994,20 +1000,13 @@ "index": "pypi", "version": "==0.9.3" }, - "pyparsing": { - "hashes": [ - "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", - "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" - ], - "version": "==2.4.0" - }, "python-dateutil": { "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], "markers": "python_version >= '2.7'", - "version": "==2.8.0" + "version": "==2.8.1" }, "python-dotenv": { "hashes": [ @@ -1019,27 +1018,26 @@ }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], - "version": "==2019.1" + "version": "==2019.3" }, "pyyaml": { "hashes": [ - "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", - "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", - "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", - "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", - "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", - "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", - "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", - "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", - "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", - "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", - "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd" + "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", + "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", + "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", + "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", + "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", + "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", + "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", + "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", + "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", + "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", + "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" ], - "index": "pypi", - "version": "==5.1.1" + "version": "==5.3" }, "requests": { "hashes": [ @@ -1051,18 +1049,10 @@ }, "requests-oauthlib": { "hashes": [ - "sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57", - "sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140" + "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" ], - "version": "==1.2.0" - }, - "rospkg": { - "hashes": [ - "sha256:c6e918e13a6a51b362fd07b9a298f64e4cb066ee1e6168ac95b690c0b609b2ab", - "sha256:dbead644008f5ba2b2a5be3aa49c30426cb9457808d1f0d9be809e5183711255" - ], - "index": "pypi", - "version": "==1.1.7" + "version": "==1.3.0" }, "rsa": { "hashes": [ @@ -1080,27 +1070,31 @@ }, "simplejson": { "hashes": [ - "sha256:067a7177ddfa32e1483ba5169ebea1bc2ea27f224853211ca669325648ca5642", - "sha256:2fc546e6af49fb45b93bbe878dea4c48edc34083729c0abd09981fe55bdf7f91", - "sha256:354fa32b02885e6dae925f1b5bbf842c333c1e11ea5453ddd67309dc31fdb40a", - "sha256:37e685986cf6f8144607f90340cff72d36acf654f3653a6c47b84c5c38d00df7", - "sha256:3af610ee72efbe644e19d5eaad575c73fb83026192114e5f6719f4901097fce2", - "sha256:3b919fc9cf508f13b929a9b274c40786036b31ad28657819b3b9ba44ba651f50", - "sha256:3dd289368bbd064974d9a5961101f080e939cbe051e6689a193c99fb6e9ac89b", - "sha256:6c3258ffff58712818a233b9737fe4be943d306c40cf63d14ddc82ba563f483a", - "sha256:75e3f0b12c28945c08f54350d91e624f8dd580ab74fd4f1bbea54bc6b0165610", - "sha256:b1f329139ba647a9548aa05fb95d046b4a677643070dc2afc05fa2e975d09ca5", - "sha256:ee9625fc8ee164902dfbb0ff932b26df112da9f871c32f0f9c1bcf20c350fe2a", - "sha256:fb2530b53c28f0d4d84990e945c2ebb470edb469d63e389bf02ff409012fe7c5" - ], - "version": "==3.16.0" + "sha256:0fe3994207485efb63d8f10a833ff31236ed27e3b23dadd0bf51c9900313f8f2", + "sha256:17163e643dbf125bb552de17c826b0161c68c970335d270e174363d19e7ea882", + "sha256:1d1e929cdd15151f3c0b2efe953b3281b2fd5ad5f234f77aca725f28486466f6", + "sha256:1ea59f570b9d4916ae5540a9181f9c978e16863383738b69a70363bc5e63c4cb", + "sha256:22a7acb81968a7c64eba7526af2cf566e7e2ded1cb5c83f0906b17ff1540f866", + "sha256:2b4b2b738b3b99819a17feaf118265d0753d5536049ea570b3c43b51c4701e81", + "sha256:4cf91aab51b02b3327c9d51897960c554f00891f9b31abd8a2f50fd4a0071ce8", + "sha256:7cce4bac7e0d66f3a080b80212c2238e063211fe327f98d764c6acbc214497fc", + "sha256:8027bd5f1e633eb61b8239994e6fc3aba0346e76294beac22a892eb8faa92ba1", + "sha256:86afc5b5cbd42d706efd33f280fec7bd7e2772ef54e3f34cf6b30777cd19a614", + "sha256:87d349517b572964350cc1adc5a31b493bbcee284505e81637d0174b2758ba17", + "sha256:926bcbef9eb60e798eabda9cd0bbcb0fca70d2779aa0aa56845749d973eb7ad5", + "sha256:9a126c3a91df5b1403e965ba63b304a50b53d8efc908a8c71545ed72535374a3", + "sha256:daaf4d11db982791be74b23ff4729af2c7da79316de0bebf880fa2d60bcc8c5a", + "sha256:fc046afda0ed8f5295212068266c92991ab1f4a50c6a7144b69364bdee4a0159", + "sha256:fc9051d249dd5512e541f20330a74592f7a65b2d62e18122ca89bf71f94db748" + ], + "version": "==3.17.0" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.12.0" + "version": "==1.14.0" }, "sqlparse": { "hashes": [ @@ -1111,11 +1105,10 @@ }, "uritemplate": { "hashes": [ - "sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd", - "sha256:1b9c467a940ce9fb9f50df819e8ddd14696f89b9a8cc87ac77952ba416e0a8fd", - "sha256:c02643cebe23fc8adb5e6becffe201185bf06c40bda5c0b4028a93f1527d011d" + "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f", + "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae" ], - "version": "==3.0.0" + "version": "==3.0.1" }, "urllib3": { "hashes": [ @@ -1134,10 +1127,10 @@ }, "websocket-client": { "hashes": [ - "sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9", - "sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a" + "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549", + "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010" ], - "version": "==0.56.0" + "version": "==0.57.0" } }, "develop": { @@ -1150,11 +1143,11 @@ }, "flake8": { "hashes": [ - "sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", - "sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8" + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" ], "index": "pypi", - "version": "==3.7.7" + "version": "==3.7.9" }, "mccabe": { "hashes": [ diff --git a/automan/api/projects/annotations/annotation_manager.py b/automan/api/projects/annotations/annotation_manager.py index 168f1944..f7104944 100644 --- a/automan/api/projects/annotations/annotation_manager.py +++ b/automan/api/projects/annotations/annotation_manager.py @@ -1,13 +1,17 @@ import json import uuid +import os from uuid import UUID +from datetime import datetime, timedelta, timezone from django.db import transaction from django.db.models import Q -from django.core.exceptions import ValidationError, ObjectDoesNotExist, FieldError +from django.core.exceptions import ( + ValidationError, ObjectDoesNotExist, PermissionDenied, FieldError) from projects.annotations.models import DatasetObject, DatasetObjectAnnotation, AnnotationProgress from projects.annotations.helpers.label_types.bb2d import BB2D from projects.annotations.helpers.label_types.bb2d3d import BB2D3D -from .models import Annotation, ArchivedLabelDataset +from .models import Annotation, ArchivedLabelDataset, FrameLock +from projects.datasets.models import LabelDataset from projects.models import Projects from api.settings import PER_PAGE, SORT_KEY from api.common import validation_check @@ -17,7 +21,9 @@ class AnnotationManager(object): def create_annotation(self, user_id, project_id, name, dataset_id): - new_annotation = Annotation(name=name, dataset_id=dataset_id, project_id=project_id) + dataset = LabelDataset.objects.filter(id=dataset_id).first() + new_annotation = Annotation( + name=name, dataset_id=dataset_id, project_id=project_id, frame=dataset.frame_count) new_annotation.save() # FIXME: state @@ -29,8 +35,7 @@ def create_annotation(self, user_id, project_id, name, dataset_id): return new_annotation.id def get_annotation(self, annotation_id): - annotation = Annotation.objects.filter(id=annotation_id, delete_flag=False).first() - + annotation = Annotation.objects.filter(id=annotation_id).first() if annotation is None: raise ObjectDoesNotExist() contents = {} @@ -41,7 +46,7 @@ def get_annotation(self, annotation_id): return contents def annotation_total_count(self, project_id): - annotations = Annotation.objects.filter(project_id=project_id, delete_flag=False) + annotations = Annotation.objects.filter(project_id=project_id) return annotations.count() def list_annotations( @@ -54,19 +59,15 @@ def list_annotations( if is_reverse is False: annotations = Annotation.objects.order_by(sort_key).filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword))[begin:begin + per_page] else: annotations = Annotation.objects.order_by(sort_key).reverse().filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword))[begin:begin + per_page] except FieldError: annotations = Annotation.objects.order_by("id").filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword))[begin:begin + per_page] - records = [] for annotation in annotations: record = {} @@ -75,6 +76,9 @@ def list_annotations( record['created_at'] = str(annotation.created_at) record['dataset_id'] = annotation.dataset_id record['archive_url'], record['file_name'] = self.get_archive_url(project_id, annotation.id) + annotation_progress = self.get_newest_annotation(annotation.id) + record['progress'] = annotation_progress.progress + record['status'] = annotation_progress.state records.append(record) contents = {} contents['count'] = self.annotation_total_count(project_id) @@ -99,17 +103,25 @@ def get_newest_annotation(self, annotation_id): return newest_annotation def delete_annotation(self, annotation_id): + archives = ArchivedLabelDataset.objects.filter(annotation_id=annotation_id) + for archive in archives: + path = archive.file_path + '/' + archive.file_name + os.remove(path) annotation = Annotation.objects.filter(id=annotation_id).first() - if annotation.delete_flag is True: - raise ObjectDoesNotExist() - annotation.delete_flag = True - annotation.save() + annotation.delete() - def get_frame_labels(self, project_id, annotation_id, frame): + def delete_annotations(self, dataset_id): + annotations = Annotation.objects.filter(dataset_id=dataset_id) + for annotation in annotations: + self.delete_annotation(annotation.id) + + def get_frame_labels(self, project_id, user_id, try_lock, annotation_id, frame): objects = DatasetObject.objects.filter( annotation_id=annotation_id, frame=frame) if objects is None: raise ObjectDoesNotExist() + if try_lock: + self.release_lock(user_id, annotation_id) records = [] count = 0 @@ -122,12 +134,14 @@ def get_frame_labels(self, project_id, annotation_id, frame): record['object_id'] = object.id record['name'] = label.name record['content'] = json.loads(label.content) - record['instance_id'] = str(object.instance) if object.instance != None else None + record['instance_id'] = str(object.instance) if object.instance is not None else None records.append(record) count += 1 labels = {} labels['count'] = count labels['records'] = records + labels['is_locked'], labels['expires_at'] = self.get_lock( + try_lock, user_id, annotation_id, frame) return labels @transaction.atomic @@ -142,6 +156,9 @@ def set_frame_label( else: raise UnknownLabelTypeError # TODO: BB3D + if not self.has_valid_lock(user_id, annotation_id, frame): + raise PermissionDenied + # TODO: bulk insert (try to use bulk_create method) for label in created_list: for v in label['content'].values(): @@ -180,12 +197,21 @@ def set_frame_label( delete_flag=True) deleted_label.save() - # FIXME: state, progress + annotation = Annotation.objects.filter(id=annotation_id).first() + objects = DatasetObject.objects.filter(annotation_id=annotation_id) + frames = [] + for object in objects: + frames.append(object.frame) + try: + progress = len(set(frames)) / annotation.frame * 100 + except ZeroDivisionError: + progress = -1 + state = 'editing' if progress < 100 else 'finished' new_progress = AnnotationProgress( annotation_id=annotation_id, user=user_id, - state='editing', - progress=0, + state=state, + progress=progress, frame_progress=frame) new_progress.save() @@ -210,6 +236,15 @@ def set_archive(self, annotation_id, file_path, file_name): file_name=file_name) new_archive.save() + old = self.get_newest_annotation(annotation_id) + new_progress = AnnotationProgress( + annotation_id=annotation_id, + user=old.user, + state='archived', + progress=old.progress, + frame_progress=old.frame_progress) + new_progress.save() + def get_archive_url(self, project_id, annotation_id): archive = ArchivedLabelDataset.objects.filter( annotation_id=annotation_id).order_by('-date').first() @@ -225,14 +260,14 @@ def get_archive_path(self, annotation_id): return archive_path def get_instances(self, annotation_id): - objects = DatasetObject.objects.filter(annotation_id=annotation_id) - records = [] - for object in objects: - records.append(str(object.instance)) - labels = {} - labels['records'] = list(set(records)) - labels['count'] = len(labels['records']) - return labels + objects = DatasetObject.objects.filter(annotation_id=annotation_id) + records = [] + for object in objects: + records.append(str(object.instance)) + labels = {} + labels['records'] = list(set(records)) + labels['count'] = len(labels['records']) + return labels def get_instance(self, annotation_id, instance_id): if self.is_valid_uuid4(instance_id) is not True: @@ -265,3 +300,48 @@ def is_valid_uuid4(self, uuid4): return True except: return False + + def get_lock(self, try_lock, user_id, annotation_id, frame): + if try_lock is not True: + return False, None + lock = FrameLock.objects.filter( + annotation_id=annotation_id, frame=frame + ).first() + new_expires_at = datetime.now(timezone.utc) + timedelta(minutes=5) + + if lock is None: + lock = FrameLock( + user=user_id, + annotation_id=annotation_id, + frame=frame, + expires_at=new_expires_at) + lock.save() + elif lock.expires_at < datetime.now(timezone.utc): + lock.user = user_id + lock.expires_at = new_expires_at + lock.save() + else: + return False, None + return True, int(new_expires_at.timestamp()) + + def release_lock(self, user_id, annotation_id): + lock = FrameLock.objects.filter( + user=user_id, + annotation_id=annotation_id + ).first() + if lock is None: + return False + lock.delete() + return True + + def has_valid_lock(self, user_id, annotation_id, frame): + lock = FrameLock.objects.filter( + user=user_id, + annotation_id=annotation_id, + frame=frame + ).first() + if lock is None: + return False + lock.expires_at = datetime.now(timezone.utc) + timedelta(minutes=5) + lock.save() + return True diff --git a/automan/api/projects/annotations/migrations/0003_framelock.py b/automan/api/projects/annotations/migrations/0003_framelock.py new file mode 100644 index 00000000..d060511f --- /dev/null +++ b/automan/api/projects/annotations/migrations/0003_framelock.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.2 on 2019-11-12 05:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('annotations', '0002_datasetobject_instance'), + ] + + operations = [ + migrations.CreateModel( + name='FrameLock', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('frame', models.IntegerField()), + ('user', models.IntegerField()), + ('expires_at', models.DateTimeField()), + ('annotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='annotations.Annotation')), + ], + ), + ] diff --git a/automan/api/projects/annotations/migrations/0004_auto_20200122_0630.py b/automan/api/projects/annotations/migrations/0004_auto_20200122_0630.py new file mode 100644 index 00000000..fcd922df --- /dev/null +++ b/automan/api/projects/annotations/migrations/0004_auto_20200122_0630.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('annotations', '0003_framelock'), + ] + + operations = [ + migrations.RemoveField( + model_name='annotation', + name='delete_flag', + ), + migrations.RemoveField( + model_name='archivedlabeldataset', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/annotations/migrations/0005_annotation_frame.py b/automan/api/projects/annotations/migrations/0005_annotation_frame.py new file mode 100644 index 00000000..66e44bd7 --- /dev/null +++ b/automan/api/projects/annotations/migrations/0005_annotation_frame.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.2 on 2020-02-12 04:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('annotations', '0004_auto_20200122_0630'), + ] + + operations = [ + migrations.AddField( + model_name='annotation', + name='frame', + field=models.IntegerField(default=0), + ), + ] diff --git a/automan/api/projects/annotations/models.py b/automan/api/projects/annotations/models.py index 74c93c47..83c3715d 100644 --- a/automan/api/projects/annotations/models.py +++ b/automan/api/projects/annotations/models.py @@ -3,15 +3,14 @@ from django.utils import timezone from projects.datasets.models import LabelDataset from projects.models import Projects -import uuid class Annotation(models.Model): dataset = models.ForeignKey(LabelDataset, on_delete=models.CASCADE) name = models.CharField(max_length=127, default='') created_at = models.DateTimeField(default=timezone.now) - delete_flag = models.BooleanField(default=False) project = models.ForeignKey(Projects, on_delete=models.CASCADE) + frame = models.IntegerField(default=0) class ArchivedLabelDataset(models.Model): @@ -19,7 +18,6 @@ class ArchivedLabelDataset(models.Model): file_name = models.CharField(max_length=255, default='') date = models.DateTimeField(default=timezone.now) progress = models.IntegerField(default=0) - delete_flag = models.BooleanField(default=False) annotation = models.ForeignKey(Annotation, on_delete=models.CASCADE) @@ -44,3 +42,10 @@ class DatasetObjectAnnotation(models.Model): delete_flag = models.BooleanField(default=False) name = models.CharField(max_length=45) content = models.CharField(max_length=511) + + +class FrameLock(models.Model): + annotation = models.ForeignKey(Annotation, on_delete=models.CASCADE) + frame = models.IntegerField() + user = models.IntegerField() + expires_at = models.DateTimeField() diff --git a/automan/api/projects/annotations/urls.py b/automan/api/projects/annotations/urls.py index a5ca9013..a603361d 100644 --- a/automan/api/projects/annotations/urls.py +++ b/automan/api/projects/annotations/urls.py @@ -6,6 +6,7 @@ url(r'^(?P\d+)/$', views.annotation, name='annotation'), url(r'^(?P\d+)/archive/$', views.download_archived_annotation, name='download_archived_annotation'), url(r'^(?P\d+)/frames/(?P\d+)/objects/$', views.frame, name='frame'), + url(r'^(?P\d+)/unlock/$', views.unlock, name='unlock'), url(r'^(?P\d+)/instances/$', views.instances, name='instances'), url(r'^(?P\d+)/instances/(?P\S+)/$', views.instance, name='instance'), ] diff --git a/automan/api/projects/annotations/views.py b/automan/api/projects/annotations/views.py index 11fecc22..6f541d59 100644 --- a/automan/api/projects/annotations/views.py +++ b/automan/api/projects/annotations/views.py @@ -72,8 +72,9 @@ def frame(request, project_id, annotation_id, frame): if request.method == 'GET': if not Permission.hasPermission(user_id, 'get_label', project_id): raise PermissionDenied + try_lock = (request.GET.get(key='try_lock') == 'true') labels = annotation_manager.get_frame_labels( - project_id, annotation_id, frame) + project_id, user_id, try_lock, annotation_id, frame) return HttpResponse(content=json.dumps(labels), status=200, content_type='application/json') else: @@ -104,8 +105,20 @@ def instances(request, project_id, annotation_id): contents = annotation_manager.get_instances(annotation_id) return HttpResponse(content=json.dumps(contents), status=200, content_type='application/json') + @api_view(['GET']) def instance(request, project_id, annotation_id, instance_id): annotation_manager = AnnotationManager() contents = annotation_manager.get_instance(annotation_id, instance_id) return HttpResponse(content=json.dumps(contents), status=200, content_type='application/json') + + +@api_view(['DELETE']) +def unlock(request, project_id, annotation_id): + username = request.user + user_id = AccountManager.get_id_by_username(username) + annotation_manager = AnnotationManager() + is_ok = annotation_manager.release_lock(user_id, annotation_id) + if is_ok: + return HttpResponse(status=204) + return HttpResponse(status=404) diff --git a/automan/api/projects/calibrations/migrations/0002_remove_calibration_delete_flag.py b/automan/api/projects/calibrations/migrations/0002_remove_calibration_delete_flag.py new file mode 100644 index 00000000..9931056e --- /dev/null +++ b/automan/api/projects/calibrations/migrations/0002_remove_calibration_delete_flag.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('calibrations', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='calibration', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/calibrations/models.py b/automan/api/projects/calibrations/models.py index c5027ecc..7d12a995 100644 --- a/automan/api/projects/calibrations/models.py +++ b/automan/api/projects/calibrations/models.py @@ -8,5 +8,4 @@ class Calibration(models.Model): created_at = models.DateTimeField(default=timezone.now) name = models.CharField(max_length=100, default='') content = models.CharField(max_length=1024, default='') - delete_flag = models.BooleanField(default=False) project = models.ForeignKey(Projects, null=True, on_delete=models.CASCADE) diff --git a/automan/api/projects/calibrations/serializer.py b/automan/api/projects/calibrations/serializer.py index 38a325a2..a47b1eb6 100644 --- a/automan/api/projects/calibrations/serializer.py +++ b/automan/api/projects/calibrations/serializer.py @@ -21,17 +21,14 @@ def list( if is_reverse is False: calibrations = Calibration.objects.order_by(sort_key).filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword))[begin:begin + per_page] else: calibrations = Calibration.objects.order_by(sort_key).reverse().filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword))[begin:begin + per_page] except FieldError: calibrations = Calibration.objects.order_by("id").filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword))[begin:begin + per_page] records = [] for calibration in calibrations: @@ -48,6 +45,5 @@ def list( @staticmethod def calibration_total_count(project_id): - calibrations = Calibration.objects.filter( - project_id=project_id, delete_flag=False) + calibrations = Calibration.objects.filter(project_id=project_id) return calibrations.count() diff --git a/automan/api/projects/datasets/dataset_manager.py b/automan/api/projects/datasets/dataset_manager.py index ca8a18d8..90783eb4 100644 --- a/automan/api/projects/datasets/dataset_manager.py +++ b/automan/api/projects/datasets/dataset_manager.py @@ -1,8 +1,12 @@ +import shutil +from django.db import transaction from django.db.models import Q from django.core.exceptions import ObjectDoesNotExist, FieldError from api.common import validation_check from api.settings import SORT_KEY, PER_PAGE from .models import LabelDataset, DatasetDatasetCandidate +from projects.originals.candidate_manager import CandidateManager +from projects.annotations.annotation_manager import AnnotationManager class DatasetManager(object): @@ -15,17 +19,14 @@ def get_datasets(self, project_id, user_id, sort_key=SORT_KEY, is_reverse=False, if is_reverse is False: datasets = LabelDataset.objects.order_by(sort_key).filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword) | Q(name__contains=search_keyword))[begin:begin + per_page] else: datasets = LabelDataset.objects.order_by(sort_key).reverse().filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword) | Q(name__contains=search_keyword))[begin:begin + per_page] except FieldError: datasets = LabelDataset.objects.order_by("id").filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword) | Q(name__contains=search_keyword))[begin:begin + per_page] records = [] for dataset in datasets: @@ -36,16 +37,19 @@ def get_datasets(self, project_id, user_id, sort_key=SORT_KEY, is_reverse=False, record['file_path'] = dataset.file_path record['name'] = dataset.name record['frame_count'] = dataset.frame_count - record['original_id'] = dataset.original_id + record['original_id'] = dataset.original records.append(record) contents = {} contents['count'] = self.dataset_total_count(project_id) contents['records'] = records return contents + def get_datasets_count_by_original(self, original_id): + datasets = LabelDataset.objects.filter(original=original_id) + return datasets.count() + def dataset_total_count(self, project_id): - datasets = LabelDataset.objects.filter( - project_id=project_id, delete_flag=False) + datasets = LabelDataset.objects.filter(project_id=project_id) return datasets.count() def create_dataset(self, name, file_path, frame_count, original_id, project_id, candidates): @@ -53,9 +57,8 @@ def create_dataset(self, name, file_path, frame_count, original_id, project_id, name=name, file_path=file_path, frame_count=frame_count, - original_id=original_id, - project_id=project_id, - delete_flag=False) + original=original_id, + project_id=project_id) new_dataset.save() for candidate in candidates: @@ -66,29 +69,38 @@ def create_dataset(self, name, file_path, frame_count, original_id, project_id, new_candidate.save() return new_dataset.id + @transaction.atomic def delete_dataset(self, admin_id, dataset_id): dataset = LabelDataset.objects.filter(id=dataset_id).first() if dataset is None: raise ObjectDoesNotExist() - dataset.delete_flag = True - dataset.save() + # check original_id exist + candidate_manager = CandidateManager() + if not candidate_manager.is_exist_original(dataset.original): + # delete candidate + candidate_manager.delete_candidate(dataset.original) + # delete dataset files (image, pcd) + shutil.rmtree(dataset.file_path) + annotation_manager = AnnotationManager() + annotation_manager.delete_annotations(dataset_id) + dataset.delete() def get_dataset(self, user_id, dataset_id): - dataset = LabelDataset.objects.filter(id=dataset_id, delete_flag=False).first() + dataset = LabelDataset.objects.filter(id=dataset_id).first() if dataset is None: raise ObjectDoesNotExist() contents = {} contents['id'] = dataset.id contents['name'] = dataset.name contents['file_path'] = dataset.file_path - contents['original_id'] = dataset.original_id + contents['original_id'] = dataset.original contents['frame_count'] = dataset.frame_count contents['created_at'] = str(dataset.created_at) contents['updated_at'] = str(dataset.updated_at) return contents def get_dataset_file_path(self, user_id, dataset_id): - dataset = LabelDataset.objects.filter(id=dataset_id, delete_flag=False).first() + dataset = LabelDataset.objects.filter(id=dataset_id).first() if dataset is None: raise ObjectDoesNotExist() return dataset.file_path diff --git a/automan/api/projects/datasets/frames/frame_manager.py b/automan/api/projects/datasets/frames/frame_manager.py index b90471ab..1485591f 100644 --- a/automan/api/projects/datasets/frames/frame_manager.py +++ b/automan/api/projects/datasets/frames/frame_manager.py @@ -9,7 +9,7 @@ class DatasetFrameManager(): @classmethod def list_dataset_frames(cls, project_id, dataset_id): dataset = LabelDataset.objects.filter( - id=dataset_id, project_id=project_id, delete_flag=False).first() + id=dataset_id, project_id=project_id).first() if dataset is None: raise ObjectDoesNotExist() @@ -29,7 +29,7 @@ def list_dataset_frames(cls, project_id, dataset_id): @classmethod def get_dataset_frame(cls, project_id, dataset_id, frame): dataset = LabelDataset.objects.filter( - id=dataset_id, project_id=project_id, delete_flag=False).first() + id=dataset_id, project_id=project_id).first() if dataset is None: raise ObjectDoesNotExist() diff --git a/automan/api/projects/datasets/migrations/0002_auto_20200108_0652.py b/automan/api/projects/datasets/migrations/0002_auto_20200108_0652.py new file mode 100644 index 00000000..f907dc9b --- /dev/null +++ b/automan/api/projects/datasets/migrations/0002_auto_20200108_0652.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.2 on 2020-01-08 06:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('datasets', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='labeldataset', + name='original', + field=models.IntegerField(default=-1), + ), + ] diff --git a/automan/api/projects/datasets/migrations/0003_remove_labeldataset_delete_flag.py b/automan/api/projects/datasets/migrations/0003_remove_labeldataset_delete_flag.py new file mode 100644 index 00000000..341bc7b1 --- /dev/null +++ b/automan/api/projects/datasets/migrations/0003_remove_labeldataset_delete_flag.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('datasets', '0002_auto_20200108_0652'), + ] + + operations = [ + migrations.RemoveField( + model_name='labeldataset', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/datasets/models.py b/automan/api/projects/datasets/models.py index 407b0ef6..34253abb 100644 --- a/automan/api/projects/datasets/models.py +++ b/automan/api/projects/datasets/models.py @@ -2,18 +2,15 @@ from django.db import models from django.utils import timezone from projects.models import Projects -from projects.originals.models import Original, DatasetCandidate +from projects.originals.models import DatasetCandidate class LabelDataset(models.Model): - original = models.ForeignKey( - Original, null=True, - related_name='project_rosbag', on_delete=models.CASCADE) + original = models.IntegerField(default=-1) created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField(default=timezone.now) file_path = models.CharField(max_length=255, default='') name = models.CharField(max_length=100, default='') - delete_flag = models.BooleanField(default=False) frame_count = models.IntegerField(default=-1) project = models.ForeignKey(Projects, null=True, on_delete=models.CASCADE) diff --git a/automan/api/projects/groups/migrations/0002_remove_groups_delete_flag.py b/automan/api/projects/groups/migrations/0002_remove_groups_delete_flag.py new file mode 100644 index 00000000..434cf071 --- /dev/null +++ b/automan/api/projects/groups/migrations/0002_remove_groups_delete_flag.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('groups', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='groups', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/groups/models.py b/automan/api/projects/groups/models.py index b8ebcdaa..e48aece6 100644 --- a/automan/api/projects/groups/models.py +++ b/automan/api/projects/groups/models.py @@ -5,7 +5,6 @@ class Groups(models.Model): project = models.ForeignKey(Projects, on_delete=models.CASCADE) name = models.CharField(max_length=255) - delete_flag = models.BooleanField(default=False) class Meta: db_table = 'groups' diff --git a/automan/api/projects/groups/serializer.py b/automan/api/projects/groups/serializer.py index 6f8c0832..25ad83f0 100644 --- a/automan/api/projects/groups/serializer.py +++ b/automan/api/projects/groups/serializer.py @@ -18,13 +18,13 @@ def get_groups(cls, project_id, sort_key=SORT_KEY, is_reverse=False, per_page=PE try: if is_reverse is False: groups = Groups.objects.order_by(sort_key).filter( - project_id=project_id, delete_flag=False) + project_id=project_id) else: groups = Groups.objects.order_by(sort_key).reverse().filter( - project_id=project_id, delete_flag=False) + project_id=project_id) except FieldError: groups = Groups.objects.order_by(SORT_KEY).filter( - project_id=project_id, delete_flag=False) + project_id=project_id) if groups is None: raise ObjectDoesNotExist() @@ -42,8 +42,7 @@ def get_groups(cls, project_id, sort_key=SORT_KEY, is_reverse=False, per_page=PE @classmethod def __get_group_users(cls, group_id): - group_users = Members.objects.filter( - group_id=group_id, delete_flag=False) + group_users = Members.objects.filter(group_id=group_id) user_ids = [int(gu.user_id) for gu in group_users] users = User.objects.filter(id__in=user_ids, is_active=1) records = [{'id': user.id, 'username': user.username} for user in users] diff --git a/automan/api/projects/jobs/serializer.py b/automan/api/projects/jobs/serializer.py index 9a354d7a..3fb1c177 100644 --- a/automan/api/projects/jobs/serializer.py +++ b/automan/api/projects/jobs/serializer.py @@ -55,7 +55,7 @@ def list_jobs(cls, project_id, sort_key, is_reverse=False, per_page=PER_PAGE, pa status, start_time, completion_time = cls.__get_job_status(job.id, job.job_type) if job.status != STATUS_MAP['unknown'] and status == STATUS_MAP['unknown']: job.unknown_started_at = datetime.now(timezone.utc) - job.status = status + job.status = status if status else STATUS_MAP['unknown'] job.started_at = start_time job.completed_at = completion_time if job.status == STATUS_MAP['unknown'] and cls.__is_unknown_time_limit(job.unknown_started_at): diff --git a/automan/api/projects/klassset/klassset_manager.py b/automan/api/projects/klassset/klassset_manager.py index cd551317..62316eb3 100644 --- a/automan/api/projects/klassset/klassset_manager.py +++ b/automan/api/projects/klassset/klassset_manager.py @@ -26,17 +26,15 @@ def set_klassset(self, project_id, user_id, klasses): return new_klassset.id def get_klassset(self, project_id): - klassset = KlassSet.objects.filter( - project_id=project_id, delete_flag=False).first() + klassset = KlassSet.objects.filter(project_id=project_id).first() return klassset def get_klassset_info(self, project_id): klassset = KlassSet.objects.filter( - project_id=project_id, delete_flag=False).first() + project_id=project_id).first() try: klasses = KlassKlassSet.objects.filter( - klass_set_id=klassset.id, - delete_flag=False) + klass_set_id=klassset.id) except Exception: klasses = [] diff --git a/automan/api/projects/members/migrations/0002_remove_members_delete_flag.py b/automan/api/projects/members/migrations/0002_remove_members_delete_flag.py new file mode 100644 index 00000000..00b4400b --- /dev/null +++ b/automan/api/projects/members/migrations/0002_remove_members_delete_flag.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='members', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/members/models.py b/automan/api/projects/members/models.py index 0e25665a..70df0ce5 100644 --- a/automan/api/projects/members/models.py +++ b/automan/api/projects/members/models.py @@ -8,7 +8,6 @@ class Members(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) group = models.ForeignKey(Groups, on_delete=models.CASCADE) project = models.ForeignKey(Projects, on_delete=models.CASCADE) - delete_flag = models.BooleanField(default=False) class Meta: db_table = 'members' diff --git a/automan/api/projects/members/serializer.py b/automan/api/projects/members/serializer.py index 19aaa48d..e8271258 100644 --- a/automan/api/projects/members/serializer.py +++ b/automan/api/projects/members/serializer.py @@ -10,8 +10,7 @@ class Meta: @classmethod def list(self, project_id): - members = Members.objects.filter( - project_id=project_id, delete_flag=False) + members = Members.objects.filter(project_id=project_id) if members is None: raise ObjectDoesNotExist() @@ -31,12 +30,12 @@ def list(self, project_id): def destroy(cls, project_id, user_id, group_id): member = Members.objects.filter( project_id=project_id, user_id=user_id, group_id=group_id).first() - member.delete_flag = True - member.save() - return member.id + id = member.id + member.delete() + return id @classmethod def get_group(cls, project_id, user_id): member = Members.objects.filter( - project_id=project_id, user_id=user_id, delete_flag=False).first() + project_id=project_id, user_id=user_id).first() return member.group.name diff --git a/automan/api/projects/migrations/0002_remove_klassklassset_delete_flag.py b/automan/api/projects/migrations/0002_remove_klassklassset_delete_flag.py new file mode 100644 index 00000000..2e6a4a74 --- /dev/null +++ b/automan/api/projects/migrations/0002_remove_klassklassset_delete_flag.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='klassklassset', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/migrations/0003_auto_20200122_0652.py b/automan/api/projects/migrations/0003_auto_20200122_0652.py new file mode 100644 index 00000000..3f3485c6 --- /dev/null +++ b/automan/api/projects/migrations/0003_auto_20200122_0652.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0002_remove_klassklassset_delete_flag'), + ] + + operations = [ + migrations.RemoveField( + model_name='klassset', + name='delete_flag', + ), + migrations.RemoveField( + model_name='projects', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/models.py b/automan/api/projects/models.py index 18f39ac2..f5fc1508 100644 --- a/automan/api/projects/models.py +++ b/automan/api/projects/models.py @@ -8,7 +8,6 @@ class Projects(models.Model): description = models.CharField(max_length=127) label_type = models.CharField(max_length=45) created_at = models.DateTimeField(default=timezone.now) - delete_flag = models.BooleanField(default=False) owner_id = models.IntegerField() class Meta: @@ -19,11 +18,9 @@ class KlassSet(models.Model): project = models.OneToOneField(Projects, on_delete=models.CASCADE) user_id = models.IntegerField() created_at = models.DateTimeField(default=timezone.now) - delete_flag = models.BooleanField(default=False) class KlassKlassSet(models.Model): name = models.CharField(max_length=100) klass_set = models.ForeignKey(KlassSet, on_delete=models.CASCADE) config = models.TextField(null=False) - delete_flag = models.BooleanField(default=False) diff --git a/automan/api/projects/originals/candidate_manager.py b/automan/api/projects/originals/candidate_manager.py new file mode 100644 index 00000000..68be590a --- /dev/null +++ b/automan/api/projects/originals/candidate_manager.py @@ -0,0 +1,14 @@ +from projects.originals.models import Original, DatasetCandidate + + +class CandidateManager(object): + def is_exist_original(self, original_id): + original = Original.objects.filter(id=original_id).first() + if original is None: + return False + return True + + def delete_candidate(self, original_id): + candidates = DatasetCandidate.objects.filter(original=original_id) + for candidate in candidates: + candidate.delete() diff --git a/automan/api/projects/originals/migrations/0002_auto_20200115_0430.py b/automan/api/projects/originals/migrations/0002_auto_20200115_0430.py new file mode 100644 index 00000000..3ceaf653 --- /dev/null +++ b/automan/api/projects/originals/migrations/0002_auto_20200115_0430.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.2 on 2020-01-15 04:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('originals', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='datasetcandidate', + name='original', + field=models.IntegerField(), + ), + ] diff --git a/automan/api/projects/originals/migrations/0003_remove_original_delete_flag.py b/automan/api/projects/originals/migrations/0003_remove_original_delete_flag.py new file mode 100644 index 00000000..1f41fbaa --- /dev/null +++ b/automan/api/projects/originals/migrations/0003_remove_original_delete_flag.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('originals', '0002_auto_20200115_0430'), + ] + + operations = [ + migrations.RemoveField( + model_name='original', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/originals/models.py b/automan/api/projects/originals/models.py index 2bbc5962..7e82e7a7 100644 --- a/automan/api/projects/originals/models.py +++ b/automan/api/projects/originals/models.py @@ -15,7 +15,6 @@ class Original(models.Model): uploaded_at = models.DateTimeField(null=True) analyzed_at = models.DateTimeField(null=True) canceled_at = models.DateTimeField(null=True) - delete_flag = models.BooleanField(default=False) project = models.ForeignKey(Projects, on_delete=models.CASCADE) storage = models.ForeignKey(Storage, on_delete=models.CASCADE) status = models.CharField(max_length=127, default='registered') @@ -44,7 +43,7 @@ class RosbagTopic(models.Model): class DatasetCandidate(models.Model): - original = models.ForeignKey(Original, on_delete=models.CASCADE) + original = models.IntegerField() data_type = models.CharField(max_length=255, default='') frame_count = models.IntegerField() analyzed_info = models.CharField(max_length=255, default='') diff --git a/automan/api/projects/originals/original_manager.py b/automan/api/projects/originals/original_manager.py index 1aed8c43..8229bed5 100644 --- a/automan/api/projects/originals/original_manager.py +++ b/automan/api/projects/originals/original_manager.py @@ -1,5 +1,6 @@ import copy import json +import shutil import os from datetime import datetime from django.core.exceptions import ObjectDoesNotExist, FieldError, ValidationError @@ -8,6 +9,7 @@ from django.utils import timezone from projects.originals.models import Original, FileType, RelatedFile, DatasetCandidate from projects.storages.serializer import StorageSerializer +from projects.datasets.dataset_manager import DatasetManager from api.common import validation_check from api.settings import SORT_KEY, PER_PAGE from automan_website.settings import STORAGE_CONFIG @@ -25,8 +27,7 @@ def register_original(self, project_id, user_id, name, file_type, size, storage_ user_id=user_id, file_type=file_type, size=size, - storage_id=storage_id, - delete_flag=False) + storage_id=storage_id) new_original.save() storage_config = copy.deepcopy(STORAGE_CONFIG) storage_config.update({'blob': name}) @@ -46,7 +47,7 @@ def update_status(self, original_id, status, dataset_candidates=None): original.analyzed_at = timezone.datetime.now() for candidate in dataset_candidates: new_dataset_candidate = DatasetCandidate( - original=original, + original=original.id, frame_count=candidate['frame_count'], data_type=candidate['data_type'], analyzed_info=json.dumps(candidate['analyzed_info']) @@ -60,10 +61,10 @@ def update_status(self, original_id, status, dataset_candidates=None): def get_original(self, project_id, original_id, status=None): if status: original = Original.objects.filter( - id=original_id, delete_flag=False, status=status).first() + id=original_id, status=status).first() else: original = Original.objects.filter( - id=original_id, delete_flag=False).first() + id=original_id).first() if original is None: raise ObjectDoesNotExist() content = { @@ -77,7 +78,6 @@ def get_original(self, project_id, original_id, status=None): 'uploaded_at': str(original.uploaded_at), 'analyzed_at': str(original.analyzed_at), 'canceled_at': str(original.canceled_at), - 'delete_flag': original.delete_flag, 'project_id': original.project.id, 'status': original.status, } @@ -92,19 +92,16 @@ def get_originals( if is_reverse is False: originals = Original.objects.order_by(sort_key).filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword), Q(status__contains=status))[begin:begin + per_page] else: originals = Original.objects.order_by(sort_key).reverse().filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword), Q(status__contains=status))[begin:begin + per_page] except FieldError: originals = Original.objects.order_by("id").filter( Q(project_id=project_id), - Q(delete_flag=False), Q(name__contains=search_keyword), Q(status__contains=status))[begin:begin + per_page] records = [] @@ -128,13 +125,13 @@ def get_originals( def get_dataset_candidates(self, project_id, original_id, data_type=""): dataset_candidates = DatasetCandidate.objects.filter( - Q(original_id=original_id), + Q(original=original_id), Q(data_type__contains=data_type)) records = [] for dataset_candidate in dataset_candidates: record = {} record['candidate_id'] = dataset_candidate.id - record['original_id'] = dataset_candidate.original.id + record['original_id'] = dataset_candidate.original record['data_type'] = dataset_candidate.data_type record['analyzed_info'] = dataset_candidate.analyzed_info record['frame_count'] = dataset_candidate.frame_count @@ -147,14 +144,14 @@ def get_dataset_candidate(self, candidate_id): raise ObjectDoesNotExist() record = {} - record['original_id'] = dataset_candidate.original.id + record['original_id'] = dataset_candidate.original record['data_type'] = dataset_candidate.data_type record['analyzed_info'] = dataset_candidate.analyzed_info record['frame_count'] = dataset_candidate.frame_count return record def original_total_count(self, project_id): - originals = Original.objects.filter(project_id=project_id, delete_flag=False) + originals = Original.objects.filter(project_id=project_id) return originals.count() def save_file(self, project_id, original_id, file): @@ -199,10 +196,22 @@ def set_related_file(self, name, file_path, file_type, target_rosbag): file_name=name, file_path=file_path) return related_file - def delete_rosbag(self, project_id, user_id, rosbag_id): - rosbag = Original.objects.filter(project_id=project_id, id=rosbag_id).first() + def delete_rosbag(self, project_id, user_id, original_id): + rosbag = Original.objects.filter(project_id=project_id, id=original_id).first() if rosbag is None: raise ObjectDoesNotExist() - rosbag.delete_flag = True - rosbag.save() + + storage = StorageSerializer().get_storage(project_id, rosbag.storage_id) + dir_path = (storage['storage_config']['mount_path'] + + storage['storage_config']['base_dir'] + + '/' + rosbag.name + '/') + + dataset_manager = DatasetManager() + if dataset_manager.get_datasets_count_by_original(original_id) == 0: + candidates = DatasetCandidate.objects.filter(original=original_id) + for candidate in candidates: + candidate.delete() + + rosbag.delete() + shutil.rmtree(dir_path) return True diff --git a/automan/api/projects/project_manager.py b/automan/api/projects/project_manager.py index 1d103d60..fa0c6f86 100644 --- a/automan/api/projects/project_manager.py +++ b/automan/api/projects/project_manager.py @@ -1,10 +1,13 @@ +import shutil from django.db.models import Q from django.core.exceptions import ObjectDoesNotExist, FieldError from projects.models import Projects from projects.members.models import Members +from projects.storages.serializer import StorageSerializer from projects.klassset.klassset_manager import KlasssetManager from api.common import validation_check from api.settings import SORT_KEY, PER_PAGE, SUPPORT_LABEL_TYPES +from api.permissions import Permission class ProjectManager(object): @@ -18,17 +21,14 @@ def get_projects(self, user_id, sort_key=SORT_KEY, is_reverse=False, per_page=PE if is_reverse is False: projects = Projects.objects.order_by(sort_key).filter( Q(id__in=project_ids), - Q(delete_flag=False), Q(name__contains=search_keyword) | Q(description__contains=search_keyword))[begin:begin + per_page] else: projects = Projects.objects.order_by(sort_key).reverse().filter( Q(id__in=project_ids), - Q(delete_flag=False), Q(name__contains=search_keyword) | Q(description__contains=search_keyword))[begin:begin + per_page] except FieldError: projects = Projects.objects.order_by("id").filter( Q(id__in=project_ids), - Q(delete_flag=False), Q(name__contains=search_keyword) | Q(description__contains=search_keyword))[begin:begin + per_page] records = [] @@ -39,6 +39,7 @@ def get_projects(self, user_id, sort_key=SORT_KEY, is_reverse=False, per_page=PE record['description'] = project.description record['label_type'] = project.label_type record['created_at'] = str(project.created_at) + record['can_delete'] = Permission.hasPermission(user_id, 'delete_project', project.id) records.append(record) try: klassset_manager = KlasssetManager() @@ -46,7 +47,7 @@ def get_projects(self, user_id, sort_key=SORT_KEY, is_reverse=False, per_page=PE record['klassset_name'] = klassset.name record['klassset_id'] = klassset.id except Exception: - record['klassset_name'] = ' ' + record['klassset_name'] = '' record['klassset_id'] = 0 contents = {} contents['count'] = self.project_total_count(user_id) @@ -58,8 +59,7 @@ def get_projects(self, user_id, sort_key=SORT_KEY, is_reverse=False, per_page=PE @classmethod def __user_projects(self, user_id): - groups = Members.objects.filter( - user_id=user_id, delete_flag=False) + groups = Members.objects.filter(user_id=user_id) project_ids = [] for group in groups: @@ -68,13 +68,12 @@ def __user_projects(self, user_id): def project_total_count(self, user_id): project_ids = self.__user_projects(user_id) - projects = Projects.objects.filter( - id__in=project_ids, delete_flag=False) + projects = Projects.objects.filter(id__in=project_ids) return projects.count() # (GET):/projects/:project_id:/ def get_project(self, project_id, user_id): - project = Projects.objects.filter(id=project_id, delete_flag=False).first() + project = Projects.objects.filter(id=project_id).first() if project is None: raise ObjectDoesNotExist() @@ -96,11 +95,15 @@ def get_project_id_by_name(self, name): return project.id def delete_project(self, project_id, user_id): - content = Projects.objects.filter(id=project_id, delete_flag=False).first() + content = Projects.objects.filter(id=project_id).first() if content is None: raise ObjectDoesNotExist() - content.delete_flag = True - content.save() + storages = StorageSerializer().get_storages(project_id) + for storage in storages: + dir_path = (storage['storage_config']['mount_path'] + + storage['storage_config']['base_dir']) + shutil.rmtree(dir_path) + content.delete() def __is_support_label_type(self, label_type): return label_type in SUPPORT_LABEL_TYPES diff --git a/automan/api/projects/storages/migrations/0002_remove_storage_delete_flag.py b/automan/api/projects/storages/migrations/0002_remove_storage_delete_flag.py new file mode 100644 index 00000000..0254624f --- /dev/null +++ b/automan/api/projects/storages/migrations/0002_remove_storage_delete_flag.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.2 on 2020-01-22 06:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('storages', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='storage', + name='delete_flag', + ), + ] diff --git a/automan/api/projects/storages/models.py b/automan/api/projects/storages/models.py index b73b760a..bda66593 100644 --- a/automan/api/projects/storages/models.py +++ b/automan/api/projects/storages/models.py @@ -11,4 +11,3 @@ class Storage(models.Model): created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField(default=timezone.now) project = models.ForeignKey(Projects, on_delete=models.CASCADE) - delete_flag = models.BooleanField(default=False) diff --git a/automan/api/projects/storages/serializer.py b/automan/api/projects/storages/serializer.py index 2596c6c4..1b54f8e7 100644 --- a/automan/api/projects/storages/serializer.py +++ b/automan/api/projects/storages/serializer.py @@ -39,19 +39,16 @@ def list( if is_reverse is False: storages = Storage.objects.order_by(sort_key).filter( Q(project_id=project_id), - Q(delete_flag=False), Q(storage_type__contains=search_keyword) | Q(storage_config__contains=search_keyword) )[begin:begin + per_page] else: storages = Storage.objects.order_by(sort_key).reverse().filter( Q(project_id=project_id), - Q(delete_flag=False), Q(storage_type__contains=search_keyword) | Q(storage_config__contains=search_keyword) )[begin:begin + per_page] except FieldError: storages = Storage.objects.order_by("id").filter( Q(project_id=project_id), - Q(delete_flag=False), Q(storage_type__contains=search_keyword) | Q(storage_config__contains=search_keyword) )[begin:begin + per_page] records = [] @@ -81,6 +78,19 @@ def get_storage(self, project_id, storage_id): } return record + def get_storages(self, project_id): + storages = Storage.objects.filter(project_id=project_id) + records = [] + for storage in storages: + record = { + 'id': storage.id, + 'storage_type': storage.storage_type, + 'storage_config': json.loads(storage.storage_config), + } + print(record) + records.append(record) + return records + @staticmethod def get_original_path(storage_type, storage_config, name): if storage_type == 'LOCAL_NFS': diff --git a/automan/middlewares/error_handle_middleware.py b/automan/middlewares/error_handle_middleware.py index 96f48ae7..7bf64a50 100644 --- a/automan/middlewares/error_handle_middleware.py +++ b/automan/middlewares/error_handle_middleware.py @@ -1,4 +1,3 @@ -import traceback from django.http import HttpResponse from django.core.exceptions import ValidationError, ObjectDoesNotExist, PermissionDenied from utility.service_log import ServiceLog diff --git a/automan/utility/service_log.py b/automan/utility/service_log.py index b5a2ad29..aa04e0b3 100644 --- a/automan/utility/service_log.py +++ b/automan/utility/service_log.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - -import requests import json import time import traceback diff --git a/front/app/assets/main-style.js b/front/app/assets/main-style.js index ea74b15e..f87c3f3a 100644 --- a/front/app/assets/main-style.js +++ b/front/app/assets/main-style.js @@ -86,10 +86,22 @@ export const mainStyle = theme => ({ marginTop: -10, marginBottom: -10, marginLeft: 1, - marginRight:1, + marginRight: 1, minHeight: '30px', padding: '0 0px' }, + tableProgress: { + marginTop: -4, + marginBottom: 0, + minHeight: '10px', + padding: '0 0px' + }, + tableProgressStr: { + marginTop: 0, + marginBottom: -6, + minHeight: '10px', + padding: '0 0px' + }, fab: { margin: theme.spacing.unit }, diff --git a/front/app/dashboard/components/annotation/entry.jsx b/front/app/dashboard/components/annotation/entry.jsx index 17b12b09..9ebddaa0 100644 --- a/front/app/dashboard/components/annotation/entry.jsx +++ b/front/app/dashboard/components/annotation/entry.jsx @@ -12,6 +12,22 @@ import { mainStyle } from 'automan/assets/main-style'; class AnnotationPage extends React.Component { constructor(props) { super(props); + this.state = { + needUpdate: false + } + } + deleteAnnotation = (annotation_id) => { + RequestClient.delete( + '/projects/' + this.props.currentProject.id + + '/annotations/' + annotation_id + '/', + null, + res => { + this.setState({ needUpdate: true }); + } + ); + }; + handleUpdate = () => { + this.setState({ needUpdate: false }); } handleClickAnnotation = annotationId => { if (this.props.currentProject.klassset.count === 0) { @@ -19,10 +35,10 @@ class AnnotationPage extends React.Component { } else { window.open( '/application/' + - this.props.currentProject.id + - '/annotations/' + - annotationId + - '/labeling_tool/', + this.props.currentProject.id + + '/annotations/' + + annotationId + + '/labeling_tool/', '_blank' ); } @@ -33,7 +49,12 @@ class AnnotationPage extends React.Component { - + diff --git a/front/app/dashboard/components/annotation/table.jsx b/front/app/dashboard/components/annotation/table.jsx index 7ce8d044..c63808e9 100644 --- a/front/app/dashboard/components/annotation/table.jsx +++ b/front/app/dashboard/components/annotation/table.jsx @@ -14,7 +14,17 @@ import CloudDownload from '@material-ui/icons/CloudDownload'; import Snackbar from '@material-ui/core/Snackbar'; import IconButton from '@material-ui/core/IconButton'; import CloseIcon from '@material-ui/icons/Close'; +import DeleteIcon from '@material-ui/icons/Delete'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import LinearProgress from "@material-ui/core/LinearProgress"; +function progressFormatter(cell, row) { + return row.progress; +} function actionFormatter(cell, row) { return row.actions; } @@ -29,6 +39,9 @@ class AnnotationTable extends React.Component { error: null, query: RequestClient.createPageQuery(), snackbar: false, + open: false, + row_id: null, + row_name: '', }; } show = () => { @@ -37,14 +50,30 @@ class AnnotationTable extends React.Component { hide = () => { this.setState({ snackbar: false }); }; + handleDialogOpen = row => { + this.setState({ + open: true, + row_id: row.id, + row_name: row.name + }); + }; + handleOK = () => { + this.setState({ open: false }); + this.props.deleteAnnotation(this.state.row_id); + }; + handleCancel = () => { + this.setState({ open: false }); + }; componentDidMount() { this.updatePage(); } componentDidUpdate(prevProps, prevState) { if ( prevProps.currentProject == null || - this.props.currentProject.id !== prevProps.currentProject.id + this.props.currentProject.id !== prevProps.currentProject.id || + this.props.needUpdate ) { + this.props.handleUpdate(); this.updatePage(); } } @@ -90,14 +119,14 @@ class AnnotationTable extends React.Component { RequestClient.get( url, this.state.query.getData(), - function(res) { + function (res) { that.setState({ total_count: res.count, data: res.records, is_loading: false }); }, - function(mes) { + function (mes) { that.setState({ is_loading: false, error: mes.message @@ -146,18 +175,23 @@ class AnnotationTable extends React.Component { ); } render() { - if ( this.state.error ) { + if (this.state.error) { return (
{this.state.error}
); } const { classes } = this.props; + const BorderLinearProgress = withStyles({ + root: { + height: 10, + }, + })(LinearProgress); let rows = []; if (!this.state.is_loading) { rows = this.state.data.map( - function(row, index) { - let actions; + function (row, index) { + let actions, progress; if ( row.archive_url != null && // TODO: tmp check row.archive_url.length > 0 @@ -166,27 +200,37 @@ class AnnotationTable extends React.Component {
+ +
+ +
+
); } else { @@ -195,27 +239,55 @@ class AnnotationTable extends React.Component { + +
+ +
+
); } + progress = ( +
+ + + {row.status} - {row.progress}% + +
); return { index: index, id: row.id, name: row.name, dataset_id: row.dataset_id, + progress: progress, actions: actions }; }.bind(this) @@ -277,7 +349,10 @@ class AnnotationTable extends React.Component { Dataset ID - + + Progress + + ]} /> + + {"Delete Annotation"} + + + Name: {this.state.row_name} + + + + + + + ); } diff --git a/front/app/dashboard/components/dataset/entry.jsx b/front/app/dashboard/components/dataset/entry.jsx index 4b0969ec..c0c8b605 100644 --- a/front/app/dashboard/components/dataset/entry.jsx +++ b/front/app/dashboard/components/dataset/entry.jsx @@ -1,4 +1,7 @@ import React from 'react'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Paper from '@material-ui/core/Paper'; @@ -12,7 +15,8 @@ class DatasetPage extends React.Component { super(props); this.state = { dataset_id: null, - formOpen: false + formOpen: false, + needUpdate: false }; } show = (dataset_id) => { @@ -24,6 +28,19 @@ class DatasetPage extends React.Component { hide = () => { this.setState({ formOpen: false }); }; + deleteDataset = (dataset_id) => { + RequestClient.delete( + '/projects/' + this.props.currentProject.id + + '/datasets/' + dataset_id + '/', + null, + res => { + this.setState({ needUpdate: true }); + } + ); + }; + handleUpdate = () => { + this.setState({ needUpdate: false }); + } render() { const { classes } = this.props; return ( @@ -35,7 +52,12 @@ class DatasetPage extends React.Component { /> - + @@ -43,4 +65,18 @@ class DatasetPage extends React.Component { } } -export default withStyles(mainStyle)(DatasetPage); +DatasetPage.propTypes = { + classes: PropTypes.object.isRequired +}; +const mapStateToProps = state => { + return { + currentProject: state.projectReducer.currentProject + }; +}; +export default compose( + withStyles(mainStyle, { name: 'DatasetPage' }), + connect( + mapStateToProps, + null + ) +)(DatasetPage); \ No newline at end of file diff --git a/front/app/dashboard/components/dataset/table.jsx b/front/app/dashboard/components/dataset/table.jsx index f23135ff..778e0a0a 100644 --- a/front/app/dashboard/components/dataset/table.jsx +++ b/front/app/dashboard/components/dataset/table.jsx @@ -7,6 +7,12 @@ import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; import CreateNewFolderIcon from '@material-ui/icons/CreateNewFolder'; +import DeleteIcon from '@material-ui/icons/Delete'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; import ResizableTable from 'automan/dashboard/components/parts/resizable_table'; import { mainStyle } from 'automan/assets/main-style'; @@ -23,7 +29,10 @@ class DatasetTable extends React.Component { data: [], is_loading: true, error: null, - query: RequestClient.createPageQuery() + query: RequestClient.createPageQuery(), + open: false, + row_id: null, + row_name: '' }; } componentDidMount() { @@ -32,11 +41,27 @@ class DatasetTable extends React.Component { componentDidUpdate(prevProps, prevState) { if ( prevProps.currentProject == null || - this.props.currentProject.id !== prevProps.currentProject.id + this.props.currentProject.id !== prevProps.currentProject.id || + this.props.needUpdate ) { + this.props.handleUpdate(); this.updatePage(); } } + handleClickOpen = row => { + this.setState({ + open: true, + row_id: row.id, + row_name: row.name + }); + }; + handleOK = () => { + this.setState({ open: false }); + this.props.deleteDataset(this.state.row_id); + }; + handleCancel = () => { + this.setState({ open: false }); + }; handleSearchChange(txt) { const query = this.state.query; if (txt === query.getSearch()) { @@ -112,6 +137,16 @@ class DatasetTable extends React.Component { + +
+ +
+
); return { @@ -180,6 +215,27 @@ class DatasetTable extends React.Component { + + {"Delete dataset?"} + + + Name: {this.state.row_name} + + + + + + + ); } diff --git a/front/app/dashboard/components/mypage/entry.jsx b/front/app/dashboard/components/mypage/entry.jsx index 9ce2aaf5..8a0c19dc 100644 --- a/front/app/dashboard/components/mypage/entry.jsx +++ b/front/app/dashboard/components/mypage/entry.jsx @@ -23,6 +23,15 @@ class MyPage extends React.Component { }; this.updateData(); } + deleteProject = (project_id) => { + RequestClient.delete( + '/projects/' + project_id + '/', + null, + res => { + this.setState({ needUpdate: true }); + } + ); + }; show = () => { this.setState({ open: true }); }; @@ -59,6 +68,7 @@ class MyPage extends React.Component { { @@ -43,12 +59,27 @@ class ProjectTable extends React.Component { data: res.records, is_loading: false }); + this.props.dispatchListProject(); }, mes => { this.setState({ error: mes.message }); } ); }; + handleDialogOpen = row => { + this.setState({ + open: true, + row_id: row.id, + row_name: row.name + }); + }; + handleOK = () => { + this.setState({ open: false }); + this.props.deleteProject(this.state.row_id); + }; + handleCancel = () => { + this.setState({ open: false }); + }; handleSearchChange = txt => { const query = this.state.query; if (txt === query.getSearch()) { @@ -90,12 +121,29 @@ class ProjectTable extends React.Component { const { classes } = this.props; let rows = []; rows = this.state.data.map((row, index) => { + let actions = '' + actions = ( +
+ +
+ +
+
+
+ ); return { description: row.description, id: row.id, name: row.name, labelType: row.label_type, - createdAt: row.created_at.slice(0, 19) + createdAt: row.created_at.slice(0, 19), + actions: actions }; }); const options = { @@ -122,7 +170,9 @@ class ProjectTable extends React.Component { searchDelayTime: 1000 }; options.onRowClick = (row, colIndex, rowIndex) => { - this.handleClick(row); + if (colIndex <= 3) { + this.handleClick(row); + } }; const fetchProp = { dataTotalSize: this.state.total_count @@ -152,12 +202,38 @@ class ProjectTable extends React.Component { Created At + + + + {"Delete Project"} + + + Name: {this.state.row_name} + + + + + + + ); } } +const mapDispatchToProps = dispatch => ({ + dispatchListProject: () => dispatch(listProject()), +}); const mapStateToProps = state => { return { projects: state.projectReducer.projects @@ -168,7 +244,7 @@ export default withRouter( withStyles(mainStyle, { name: 'ProjectTable' }), connect( mapStateToProps, - null + mapDispatchToProps ) )(ProjectTable) ); diff --git a/front/app/dashboard/components/original/candidateSelect2D.jsx b/front/app/dashboard/components/original/candidateSelect2D.jsx index 9d8b22c7..3a98a4e6 100644 --- a/front/app/dashboard/components/original/candidateSelect2D.jsx +++ b/front/app/dashboard/components/original/candidateSelect2D.jsx @@ -20,7 +20,7 @@ class CandidateSelect2D extends React.Component { } componentDidMount() { const original_id = this.props.original_id; - this.props.handleSetJobConfig({original_id: this.props.original_id}); + this.props.handleSetJobConfig({ original_id: this.props.original_id }); const candidates = this.props.handleGetJobConfig('candidates'); this.setState({ candidates: candidates }); let url = @@ -32,7 +32,7 @@ class CandidateSelect2D extends React.Component { data => { this.setState({ candidates_2d: data.records }); }, - () => {} + () => { } ); } handleChangeCandidate = e => { @@ -44,6 +44,7 @@ class CandidateSelect2D extends React.Component { } this.setState({ candidates: candidates }); this.props.handleSetJobConfig('candidates', candidates); + this.props.handleSelect(candidates.length != 0); }; render() { const { classes } = this.props; diff --git a/front/app/dashboard/components/original/candidateSelect2D3D.jsx b/front/app/dashboard/components/original/candidateSelect2D3D.jsx index 26d8c72b..f291873d 100644 --- a/front/app/dashboard/components/original/candidateSelect2D3D.jsx +++ b/front/app/dashboard/components/original/candidateSelect2D3D.jsx @@ -22,7 +22,7 @@ class CandidateSelect2D3D extends React.Component { } componentDidMount() { const original_id = this.props.original_id; - this.props.handleSetJobConfig({original_id: this.props.original_id}); + this.props.handleSetJobConfig({ original_id: this.props.original_id }); const candidates = this.props.handleGetJobConfig('candidates'); this.setState({ candidates: candidates }); let urlBase = @@ -35,7 +35,7 @@ class CandidateSelect2D3D extends React.Component { data => { this.setState({ candidates_2d: data.records }); }, - () => {} + () => { } ); RequestClient.get( pcdUrl, @@ -43,7 +43,7 @@ class CandidateSelect2D3D extends React.Component { data => { this.setState({ candidates_3d: data.records }); }, - () => {} + () => { } ); } handleChangeCandidate = e => { @@ -55,6 +55,12 @@ class CandidateSelect2D3D extends React.Component { } this.setState({ candidates: candidates }); this.props.handleSetJobConfig('candidates', candidates); + + let is_2d_selected = this.state.candidates_2d.some( + x => candidates.includes(x.candidate_id)); + let is_3d_selected = this.state.candidates_3d.some( + x => candidates.includes(x.candidate_id)); + this.props.handleSelect(is_2d_selected && is_3d_selected); }; render() { const { classes } = this.props; diff --git a/front/app/dashboard/components/original/entry.jsx b/front/app/dashboard/components/original/entry.jsx index 5840c3e7..ce4d18f7 100644 --- a/front/app/dashboard/components/original/entry.jsx +++ b/front/app/dashboard/components/original/entry.jsx @@ -27,6 +27,17 @@ class OriginalPage extends React.Component { needUpdate: false }; } + deleteOrig = (original_id) => { + RequestClient.delete( + '/projects/' + this.props.currentProject.id + + '/originals/' + original_id + '/', + null, + res => { + this.setState({ needUpdate: true }); + } + ); + }; + show = () => { this.setState({ formOpen: true }); }; @@ -55,7 +66,7 @@ class OriginalPage extends React.Component { RequestClient.post( url, data, - res => {this.setState({ message: null });}, + res => { this.setState({ message: null }); }, mes => { this.setState({ error: mes.message, @@ -86,6 +97,7 @@ class OriginalPage extends React.Component { hide={this.hide} handleUpdate={this.handleUpdate} needUpdate={this.state.needUpdate} + deleteOrig={this.deleteOrig} /> diff --git a/front/app/dashboard/components/original/extractorForm.jsx b/front/app/dashboard/components/original/extractorForm.jsx index 57c6c09f..62d96dba 100644 --- a/front/app/dashboard/components/original/extractorForm.jsx +++ b/front/app/dashboard/components/original/extractorForm.jsx @@ -28,11 +28,12 @@ class ExtractorForm extends React.Component { query: RequestClient.createPageQuery(), jobType: 'EXTRACTOR', jobConfig: {}, - activeStep: 0 + activeStep: 0, + selected: false }; } componentDidUpdate(prevProps) { - if ( prevProps.original_id !== this.props.original_id ) { + if (prevProps.original_id !== this.props.original_id) { this.setState({ activeStep: 0, jobConfig: {} @@ -83,11 +84,14 @@ class ExtractorForm extends React.Component { let step = this.state.activeStep - 1; this.setState({ activeStep: step }); }; + handleSelect = (is_selected) => { + this.setState({ selected: is_selected }) + } isLastStep() { return this.state.activeStep === this.totalSteps() - 1; } getStepContent(step) { - if(this.props.original_id === 0) + if (this.props.original_id === 0) return 'Unknown type' switch (step) { case 0: @@ -97,6 +101,7 @@ class ExtractorForm extends React.Component { original_id={this.props.original_id} handleSetJobConfig={this.handleSetJobConfig} handleGetJobConfig={this.handleGetJobConfig} + handleSelect={this.handleSelect} /> ); } else if (this.props.currentProject.label_type === 'BB2D3D') { @@ -105,6 +110,7 @@ class ExtractorForm extends React.Component { original_id={this.props.original_id} handleSetJobConfig={this.handleSetJobConfig} handleGetJobConfig={this.handleGetJobConfig} + handleSelect={this.handleSelect} /> ); } @@ -173,15 +179,16 @@ class ExtractorForm extends React.Component { Submit ) : ( - - )} + )} diff --git a/front/app/dashboard/components/original/table.jsx b/front/app/dashboard/components/original/table.jsx index b6b8fe5f..3dcd84b1 100644 --- a/front/app/dashboard/components/original/table.jsx +++ b/front/app/dashboard/components/original/table.jsx @@ -13,6 +13,12 @@ import IconButton from '@material-ui/core/IconButton'; import CloseIcon from '@material-ui/icons/Close'; import ResizableTable from 'automan/dashboard/components/parts/resizable_table'; import { mainStyle } from 'automan/assets/main-style'; +import DeleteIcon from '@material-ui/icons/Delete'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; function actionFormatter(cell, row) { @@ -27,9 +33,26 @@ class OriginalTable extends React.Component { total_count: 0, data: [], error: null, - query: RequestClient.createPageQuery() + query: RequestClient.createPageQuery(), + open: false, + row_id: null, + row_name: '' }; } + handleClickOpen = row => { + this.setState({ + open: true, + row_id: row.id, + row_name: row.name + }); + }; + handleOK = () => { + this.setState({ open: false }); + this.props.deleteOrig(this.state.row_id) + }; + handleCancel = () => { + this.setState({ open: false }); + }; componentDidMount() { this.updateData(); } @@ -105,7 +128,7 @@ class OriginalTable extends React.Component { actions = (
-
+
+ +
+ +
+
); return { @@ -188,7 +221,7 @@ class OriginalTable extends React.Component { Status - + ]} /> + + {"Delete raw data?"} + + + Name: {this.state.row_name} + + + + + + +
); } diff --git a/front/app/labeling_tool/actions/annotation_action.js b/front/app/labeling_tool/actions/annotation_action.js new file mode 100644 index 00000000..c3d6cd41 --- /dev/null +++ b/front/app/labeling_tool/actions/annotation_action.js @@ -0,0 +1,10 @@ +const PREFIX = 'annotation/' +export const SET_TARGET_LABEL = PREFIX + 'set_target_label'; + + +export function setTargetLabel(target) { + return dispatch => dispatch({ + type: SET_TARGET_LABEL, + label: target + }); +} diff --git a/front/app/labeling_tool/actions/tool_action.js b/front/app/labeling_tool/actions/tool_action.js new file mode 100644 index 00000000..bdc9386a --- /dev/null +++ b/front/app/labeling_tool/actions/tool_action.js @@ -0,0 +1,53 @@ +const PREFIX = 'tool/' +export const SET_TOOL_ANNOTATION = PREFIX + 'set_annotation'; +export const SET_TOOL_KLASS_SET = PREFIX + 'set_klass_set'; +export const SET_TOOL_HISTORY = PREFIX + 'set_history'; +export const SET_TOOL_CLIPBOARD = PREFIX + 'set_clipboard'; +export const SET_TOOL_CONTROLS = PREFIX + 'set_controls'; +export const SET_TOOL_LABEL_TOOL = PREFIX + 'set_label_tool'; +export const ADD_TOOL_WITH_INDEX= PREFIX + 'add_tool'; + + +export function setAnnotation(target) { + return { + type: SET_TOOL_ANNOTATION, + annotation: target + }; +} +export function setKlassSet(target) { + return { + type: SET_TOOL_KLASS_SET, + klassSet: target + }; +} +export function setHistory(target) { + return { + type: SET_TOOL_HISTORY, + history: target + }; +} +export function setClipboard(target) { + return { + type: SET_TOOL_CLIPBOARD, + clipboard: target + }; +} +export function setControls(target) { + return { + type: SET_TOOL_CONTROLS, + controls: target + }; +} +export function setLabelTool(target) { + return { + type: SET_TOOL_LABEL_TOOL, + labelTool: target + }; +} +export function addTool(idx, target) { + return { + type: ADD_TOOL_WITH_INDEX, + idx: idx, + tool: target + }; +} diff --git a/front/app/labeling_tool/annotation.js b/front/app/labeling_tool/annotation.js index a81dba9f..0d695078 100644 --- a/front/app/labeling_tool/annotation.js +++ b/front/app/labeling_tool/annotation.js @@ -6,74 +6,78 @@ import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import ListSubheader from '@material-ui/core/ListSubheader'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; import classNames from 'classnames'; import RequestClient from 'automan/services/request-client'; +import { setTargetLabel } from './actions/annotation_action'; +import { setAnnotation } from './actions/tool_action'; + class Annotation extends React.Component { - _labelTool = null; - _controls = null; - _klassSet = null; // data _deleted = null; - _targetLabel = null; // status _loaded = true; _nextId = -1; constructor(props) { super(props); - this._labelTool = props.labelTool; - this._controls = props.controls; this.state = { + prevInstanceIds: null, instanceIds: null, labels: null }; - props.getRef(this); + props.dispatchSetAnnotation(this); } - init(klassSet, history) { - this._klassSet = klassSet; - this._history = history; - return new Promise((resolve, reject) => { - resolve(); - }); + init() { + return Promise.resolve(); + } + getTools() { + return this.props.controls.getTools(); } isLoaded() { return this._loaded; } - load(frameNumber) { - if (!this._loaded) { - return Promise.reject(); + loadPrevFrame(frameNumber) { + if (frameNumber == 0) { + return Promise.resolve(); } - this._removeAll(); - this._nextId = -1; - this._history.resetHistory(); return new Promise((resolve, reject) => { - this._deleted = []; - RequestClient.get( - this._labelTool.getURL('frame_labels', frameNumber), - null, + this.props.labelTool.getURL('frame_labels', frameNumber - 1), + {try_lock: false}, res => { - const labels = new Map(), instanceIds = new Map(); + const instanceIds = new Map(); res.records.forEach(obj => { - let klass = this._klassSet.getByName(obj.name); + if (obj.instance_id == null) { + return; + } + let klass = this.props.klassSet.getByName(obj.name); let bboxes = {}; - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { const id = tool.candidateId; - if (obj.content[id] != null) { - bboxes[id] = tool.createBBox(obj.content[id]); + if (obj.content[id] != null && tool.createWipeBBox) { + bboxes[id] = tool.createWipeBBox(obj.content[id], klass); } }); - let label = new Label(this, obj.object_id, obj.instance_id, klass, bboxes); - labels.set(label.id, label); - if (label.instanceId != null) { - instanceIds.set(label.instanceId, label); - } + // TODO: add class + let label = { + //obj.object_id, + instanceId: obj.instance_id, + klass: klass, + bbox: bboxes, + select(flag) { + for(let id in this.bbox) { + this.bbox[id].select(flag); + } + } + }; + instanceIds.set(label.instanceId, label); }); - this.setState({ labels, instanceIds }, () => { - this._loaded = true; + this.setState({ prevInstanceIds: instanceIds }, () => { resolve(); }); }, @@ -81,9 +85,57 @@ class Annotation extends React.Component { reject(err); } ); - }); } + load(frameNumber) { + if (!this._loaded) { + return Promise.reject(); + } + this._removeAll(); + this._nextId = -1; + this.props.history.resetHistory(); + return this.loadPrevFrame(frameNumber) + .then(() => new Promise((resolve, reject) => { + this._deleted = []; + + RequestClient.get( + this.props.labelTool.getURL('frame_labels', frameNumber), + {try_lock: true}, + res => { + const labels = new Map(), instanceIds = new Map(); + const isLocked = res.is_locked, + expiresAt = res.expires_at; + res.records.forEach(obj => { + let klass = this.props.klassSet.getByName(obj.name); + let bboxes = {}; + this.getTools().forEach(tool => { + const id = tool.candidateId; + if (obj.content[id] != null) { + bboxes[id] = tool.createBBox(obj.content[id]); + } + }); + let label = new Label(this, obj.object_id, obj.instance_id, klass, bboxes); + labels.set(label.id, label); + if (label.instanceId != null) { + instanceIds.set(label.instanceId, label); + } + }); + this.setState({ labels, instanceIds }, () => { + this._loaded = true; + resolve(); + }); + + if (!isLocked) { + window.alert('This frame is locked.'); + } + }, + err => { + reject(err); + } + ); + + })); + } isChanged() { if (this.state.labels == null) { return false; @@ -91,7 +143,7 @@ class Annotation extends React.Component { if (this._deleted.length > 0) { return true; } - if (!this._history.hasUndo()) { + if (!this.props.history.hasUndo()) { // check by history return false; } @@ -123,7 +175,7 @@ class Annotation extends React.Component { deleted: deleted }; RequestClient.post( - this._labelTool.getURL('frame_labels', this._controls.getFrameNumber()), + this.props.labelTool.getURL('frame_labels', this.props.controls.getFrameNumber()), data, () => { resolve(); @@ -135,11 +187,11 @@ class Annotation extends React.Component { }); } getTarget() { - return this._targetLabel; + return this.props.targetLabel; } setTarget(tgt) { let next = this.getLabel(tgt), - prev = this._targetLabel; + prev = this.props.targetLabel; if (prev != null && next != null && next.id === prev.id) { return prev; } @@ -149,9 +201,9 @@ class Annotation extends React.Component { if (next != null) { next.setTarget(true); } - this._targetLabel = next; + this.props.dispatchSetTargetLabel(next); // table dom events - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { tool.updateTarget(prev, next); }); return next; @@ -159,11 +211,11 @@ class Annotation extends React.Component { create(klass, bbox) { if (klass == null) { let txt = 'Label create error: Error Class "' + klass + '"'; - this._controls.error(txt); + this.props.controls.error(txt); return null; } const label = new Label(this, this._nextId--, null, klass, bbox); - this._history.addHistory([label], 'create'); + this.props.history.addHistory([label], 'create'); this.setState(state => { const labels = new Map(state.labels); labels.set(label.id, label); @@ -175,16 +227,16 @@ class Annotation extends React.Component { let label = this.getLabel(id); if (label == null) { let txt = 'Label change Class error: Error selector "' + id + '"'; - this._controls.error(txt); + this.props.controls.error(txt); return; } if (klass == null) { let txt = 'Label change Class error: Error Class "' + klass + '"'; - this._controls.error(txt); + this.props.controls.error(txt); return; } label.setKlass(klass); - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { tool.updateBBox(label); }); } @@ -192,12 +244,12 @@ class Annotation extends React.Component { let label = this.getLabel(id); if (label == null) { let txt = 'Label add BBox error: Error selector "' + id + '"'; - this._controls.error(txt); + this.props.controls.error(txt); return; } if (label.has(candidateId)) { let txt = `Label add BBox error: this BBox is already attached in "${id}"`; - this._controls.error(txt); + this.props.controls.error(txt); return; } label.bbox[candidateId] = bbox; @@ -207,11 +259,11 @@ class Annotation extends React.Component { let label = this.getLabel(id); if (label == null) { let txt = 'Label remove BBox error: Error selector "' + id + '"'; - this._controls.error(txt); + this.props.controls.error(txt); return; } if (label.has(candidateId)) { - const tool = this._controls.getToolFromCandidateId(candidateId); + const tool = this.props.controls.getToolFromCandidateId(candidateId); tool.disposeBBox(label.bbox[candidateId]); label.bbox[candidateId] = null; //label.tableItem.addClass('has-image-bbox'); @@ -221,22 +273,22 @@ class Annotation extends React.Component { let label = this.getLabel(id); if (label == null) { let txt = 'Label remove error: Error selector "' + id + '"'; - this._controls.error(txt); + this.props.controls.error(txt); return; } - this._history.addHistory([label], 'delete'); + this.props.history.addHistory([label], 'delete'); - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { if (label.bbox[tool.candidateId] != null) { tool.disposeBBox(label.bbox[tool.candidateId]); } }); - const tgt = this._targetLabel; + const tgt = this.props.targetLabel; if (tgt != null && label.id === tgt.id) { - this._targetLabel = null; + this.props.dispatchSetTargetLabel(null); tgt.setTarget(false); - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { tool.updateTarget(tgt, null); }); } @@ -267,10 +319,10 @@ class Annotation extends React.Component { } // methods to history createHistory(label, hist = null) { - return this._history.createHistory([label], 'change', hist); + return this.props.history.createHistory([label], 'change', hist); } addHistory() { - this._history.addHistory(null); + this.props.history.addHistory(null); } createFromHistory(objects) { let labelList = []; @@ -280,7 +332,7 @@ class Annotation extends React.Component { continue; } let bboxes = {}; - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { const id = tool.candidateId; if (obj.content[id] != null) { bboxes[id] = tool.createBBox(obj.content[id]); @@ -298,7 +350,7 @@ class Annotation extends React.Component { for (let label of labelList) { labels.set(label.id, label); if (label.instanceId != null) { - instanceIds.set(label.instanceId); + instanceIds.set(label.instanceId, label); } } return { labels, instanceIds }; @@ -306,8 +358,8 @@ class Annotation extends React.Component { return labelList.slice(); } removeFromHistory(objects) { - const tools = this._controls.getTools(); - const tgt = this._targetLabel; + const tools = this.getTools(); + const tgt = this.props.targetLabel; const removeIds = [], removeInstanceIds = []; for (let obj of objects) { let label = this.getLabel(obj.id); @@ -318,9 +370,9 @@ class Annotation extends React.Component { } }); if (tgt != null && label.id === tgt.id) { - this._targetLabel = null; + this.props.dispatchSetTargetLabel(null); tgt.setTarget(false); - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { tool.updateTarget(tgt, null); }); } @@ -351,8 +403,8 @@ class Annotation extends React.Component { let target = []; if (isAll) { target = Array.from(this.state.labels.values()); - } else if (this._targetLabel != null) { - target = [this._targetLabel]; + } else if (this.props.targetLabel != null) { + target = [this.props.targetLabel]; } return target.map(label => label.toObject()); } @@ -365,9 +417,9 @@ class Annotation extends React.Component { if (instanceId !== null && this.state.instanceIds.has(instanceId)) { instanceId = null; } - let klass = this._klassSet.getByName(obj.name); + let klass = this.props.klassSet.getByName(obj.name); let bboxes = {}; - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { const id = tool.candidateId; if (obj.content[id] != null) { bboxes[id] = tool.createBBox(obj.content[id]); @@ -378,7 +430,7 @@ class Annotation extends React.Component { pastedLabels.push(label); }); - this._history.addHistory(pastedLabels, 'create'); + this.props.history.addHistory(pastedLabels, 'create'); this.setState(state => { const labels = new Map(state.labels); const instanceIds = new Map(state.instanceIds); @@ -394,14 +446,14 @@ class Annotation extends React.Component { // private _removeAll() { - this._controls.selectLabel(null); + this.props.controls.selectLabel(null); if (this.state.labels == null) { return; } this._loaded = false; this.state.labels.forEach(label => { - this._controls.getTools().forEach(tool => { + this.getTools().forEach(tool => { const id = tool.candidateId; if (label.bbox[id] != null) { tool.disposeBBox(label.bbox[id]); @@ -409,8 +461,22 @@ class Annotation extends React.Component { }); label.dispose(); }); - this._targetLabel = null; - this.setState({labels: null, instanceIds: null}); + if (this.state.prevInstanceIds != null) { + this.state.prevInstanceIds.forEach(label => { + this.getTools().forEach(tool => { + const id = tool.candidateId; + if (label.bbox[id] != null) { + tool.disposeWipeBBox(label.bbox[id]); + } + }); + }); + } + this.props.dispatchSetTargetLabel(null); + this.setState({ + labels: null, + instanceIds: null, + prevInstanceIds: null + }); } renderList(classes) { if (this.state.labels === null) { @@ -422,7 +488,7 @@ class Annotation extends React.Component { ); @@ -447,7 +513,23 @@ class Annotation extends React.Component { ); } } -export default Annotation; +const mapStateToProps = state => ({ + targetLabel: state.annotation.targetLabel, + labelTool: state.tool.labelTool, + controls: state.tool.controls, + klassSet: state.tool.klassSet, + history: state.tool.history, +}); +const mapDispatchToProps = dispatch => ({ + dispatchSetTargetLabel: target => dispatch(setTargetLabel(target)), + dispatchSetAnnotation: target => dispatch(setAnnotation(target)) +}); +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ) +)(Annotation); class LabelItem extends React.Component { constructor(props) { @@ -501,13 +583,17 @@ class Label { this._annotationTool = annotationTool; this.id = id; this.instanceId = instanceId; + if (this.instanceId && annotationTool.state.prevInstanceIds) { + this.prevLabel = + annotationTool.state.prevInstanceIds.get(this.instanceId) + } this.isChanged = this.id < 0; this.isTarget = false; this.klass = klass; this.minSize = klass.getMinSize(); this.bbox = {}; - this._annotationTool._controls.getTools().forEach(tool => { + this._annotationTool.getTools().forEach(tool => { const id = tool.candidateId; if (bbox[id] == null) { this.bbox[id] = null; @@ -552,6 +638,9 @@ class Label { if (this.labelItem != null) { this.labelItem.updateTarget(); } + if (this.prevLabel) { + this.prevLabel.select(val); + } } toIDString() { return `#${this.id < 0 ? '___' : this.id}`; @@ -583,7 +672,7 @@ class Label { if (this.instanceId != null) { ret.instanceId = this.instanceId; } - this._annotationTool._controls.getTools().forEach(tool => { + this._annotationTool.getTools().forEach(tool => { const id = tool.candidateId; if (!this.has(id)) { return; @@ -601,7 +690,7 @@ class Label { klass: this.klass, content: {} }; - this._annotationTool._controls.getTools().forEach(tool => { + this._annotationTool.getTools().forEach(tool => { const id = tool.candidateId; if (!this.has(id)) { return; @@ -618,7 +707,7 @@ class Label { } this.klass = obj.klass; this.instanceId = obj.instanceId; - this._annotationTool._controls.getTools().forEach(tool => { + this._annotationTool.getTools().forEach(tool => { const id = tool.candidateId; const content = obj.content[id]; if (content == null) { diff --git a/front/app/labeling_tool/base_label_tool.jsx b/front/app/labeling_tool/base_label_tool.jsx index 0b08224f..309f66bd 100644 --- a/front/app/labeling_tool/base_label_tool.jsx +++ b/front/app/labeling_tool/base_label_tool.jsx @@ -1,252 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; + import Controls from 'automan/labeling_tool/controls'; import RequestClient from 'automan/services/request-client'; +import { setLabelTool } from './actions/tool_action'; -let annotation, imageLabelTool, pcdLabelTool, controls, klassSet; - -const toolStatus = { -}; -const innerStatus = { - loaded: true -}; - -// base-labeltool public methods -const LabelTool = { - isEditable() { - return LabelTool.isLoaded(); - }, - loadFrame(num) { - let promise = new Promise((resolve, reject) => { - savePromise - .then(() => { - toolStatus.frameNumber = num; - toolStatus.pageBox[0].placeholder = - (num + 1) + '/' + toolStatus.frameLength; - toolStatus.pageBox.val(''); - - // load something (image, pcd) - return Promise.all( - toolStatus.tools.map( - tool => - new Promise((resolve, reject) => { - const candidateId = tool.candidateId; - const fname = toolStatus.filenames[candidateId][num]; - if (typeof fname === 'string') { - resolve(); - return; - } - RequestClient.get( - LabelTool.getURL('image_url', candidateId), - null, - res => { - RequestClient.getBinaryAsURL( - res, - blobUrl => { - toolStatus.filenames[candidateId][num] = blobUrl; - resolve(); - }, - e => { - reject(e); - } - ); - }, - e => { - reject(e); - } - ); - }) - ) - ); - }) - .then(() => { - // load annotation - let promises = toolStatus.tools.map(tool => tool.load()); - promises.push(annotation.load(num)); - return Promise.all(promises); - }) - .then( - () => { - // all loaded - controls.update(); - innerStatus.loaded = true; - resolve(); - }, - e => { - // check annotation load error - controls.error(e); - innerStatus.loaded = true; - reject(e); - } - ); - }); - return promise; - }, - reloadFrame() { - // load annotation - LabelTool.selectLabel(null); - - innerStatus.loaded = false; - return annotation.load(toolStatus.frameNumber).then( - () => { - // annotation loaded - controls.update(); - innerStatus.loaded = true; - }, - e => { - // check annotation load error - controls.error(e); - innerStatus.loaded = true; - return Promise.reject(e); - } - ); - }, - saveFrame() { - // promise function!! - if (!LabelTool.isLoaded()) { - return Promise.reject('Duplicate save'); - } - return LabelTool.saveStatus() - .then(() => annotation.save()) - .then(() => LabelTool.reloadFrame()); - }, - saveStatus() { - // promise function!! - if (!LabelTool.isLoaded()) { - return Promise.reject('Duplicate save status'); - } - return new Promise((resolve, reject) => { - // save frameNumber - resolve(); - }); - }, - nextFrame: function(count) { - if (count == undefined) { - count = toolStatus.skipFrameCount; - } - LabelTool.moveFrame(count); - }, - previousFrame: function(count) { - if (count == undefined) { - count = toolStatus.skipFrameCount; - } - LabelTool.moveFrame(-count); - }, - moveFrame(cnt) { - // TODO: check type of'cnt' - let newFrame = toolStatus.frameNumber + cnt; - newFrame = Math.max(newFrame, 0); - newFrame = Math.min(toolStatus.frameLength - 1, newFrame); - if (isFinite(newFrame)) { - return LabelTool.setFrame(newFrame); - } - return false; - }, - resetBBoxes() { - LabelTool.setFrame(toolStatus.frameNumber); - }, - setFrame(num) { - if (!LabelTool.isLoaded()) { - return false; - } - // TODO: num check - if (toolStatus.frameNumber !== num) { - num = parseInt(num); - if (isNaN(num) || num < 0 || toolStatus.frameLength <= num) { - return false; - } - this.loadFrame(num).catch(e => { - controls.error(e); - }); - } - return true; - }, -}; - -// base-labeltool internal functions -const initializeAll = function() { - klassSet = new KlassSet(LabelTool); - annotation = new Annotation(LabelTool); - imageLabelTool = new ImageLabelTool(LabelTool); - pcdLabelTool = new PCDLabelTool(LabelTool); - controls = new Controls(LabelTool, imageLabelTool, pcdLabelTool); - LabelTool.controls = controls; - initializeBase() - .then(() => { - return Promise.all([klassSet.init(), annotation.init()]); - }) - .then( - () => { - controls.init(); - toolStatus.tools.forEach(tool => { - tool.init(); - }); - toolStatus.tools[toolStatus.activeTool].setActive(true); - - initializeEvent(); - - LabelTool.loadFrame(0).then(() => { - // TODO: some check - }); - }, - e => { - controls.error(e); - } - ); -}; -const initializeBase = function() { - // promise function!! - const pathItems = window.location.pathname.split('/'); - toolStatus.projectId = parseInt(pathItems[2]); - toolStatus.annotationId = parseInt(pathItems[4]); - - toolStatus.pageBox = $('#page_num'); - toolStatus.nextFrameButton = $('#next-frame-button'); - toolStatus.prevFrameButton = $('#previous-frame-button'); - toolStatus.frameSkipText = $('#frame-skip'); - - return new Promise((resolve, reject) => { - RequestClient.get( - LabelTool.getURL('project'), - null, - res => { - toolStatus.projectInfo = res; - - // load labeling tools - const LABEL_TYPES = { - BB2D: { - tools: [imageLabelTool, pcdLabelTool] - }, - BB2D3D: { - tools: [imageLabelTool, pcdLabelTool] - } - }; - const type = LABEL_TYPES[res.label_type]; - if (type == null) { - reject('Tool type error [' + res.label_type + ']'); - return; - } - toolStatus.labelType = type; - toolStatus.tools = type.tools; - - resolve(); - }, - err => { - reject(err); - } - ); - }) - -}; - - -// init all when dom loaded -//$(initializeAll); - -export default class LabelTool_ extends React.Component { +class LabelTool extends React.Component { // components controls = null; // informations @@ -425,6 +189,9 @@ export default class LabelTool_ extends React.Component { ret = this.filenames[candidateId][frameNumber-1]; break; } + case 'unlock': + ret = ANNOTATION_ROOT + 'unlock/'; + break; /* Add more request urls */ @@ -444,26 +211,32 @@ export default class LabelTool_ extends React.Component { isLoaded: false, isInitialized: false }; - //this.klassSet = new KlassSet(this); - //this.annotation = new Annotation(this, this.klassSet); - //this.controls = React.createRef(); - - this.initializeBase().then(() => { - return new Promise((resolve, reject) => { - this.mountHandle = () => { - resolve(); - }; - this.setState({isInitialized: true}); - }); - }).then(() => { - this.initializeEvent(); - return this.controls.loadFrame(0); - }).catch(e => { - this.errorMessage = e; - console.error(e); - // ****** - }); + props.dispatchSetLabelTool(this); + + const pathItems = window.location.pathname.split('/'); + this.projectId = parseInt(pathItems[2]); + this.annotationId = parseInt(pathItems[4]); + + this.initializeBase() + .then(() => { + return new Promise((resolve, reject) => { + this.mountHandle = () => { + resolve(); + }; + this.setState({isInitialized: true}); + }); + }) + .then(() => { + this.initializeEvent(); + + return this.controls.loadFrame(0); + }) + .catch(e => { + this.errorMessage = e; + console.error(e); + // ****** + }); } // get project information @@ -534,9 +307,6 @@ export default class LabelTool_ extends React.Component { }); } initializeBase() { - const pathItems = window.location.pathname.split('/'); - this.projectId = parseInt(pathItems[2]); - this.annotationId = parseInt(pathItems[4]); return this.initProject() .then(() => this.initAnnotation()) .then(() => this.initDataset()) @@ -577,3 +347,16 @@ export default class LabelTool_ extends React.Component { } }; +const mapStateToProps = state => ({ + labelTool: state.tool.labelTool +}); +const mapDispatchToProps = dispatch => ({ + dispatchSetLabelTool: target => dispatch(setLabelTool(target)) +}); +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ) +)(LabelTool); + diff --git a/front/app/labeling_tool/clipboard.jsx b/front/app/labeling_tool/clipboard.jsx index cf19482e..f182591e 100644 --- a/front/app/labeling_tool/clipboard.jsx +++ b/front/app/labeling_tool/clipboard.jsx @@ -3,29 +3,30 @@ import ReactDOM from 'react-dom'; import Grid from '@material-ui/core/Grid'; import Button from '@material-ui/core/Button'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; + +import { setClipboard } from './actions/tool_action'; class Clipboard extends React.Component { constructor(props) { super(props); - this.annotation = null; - this._controls = props.controls; this.state = { copy: null }; - props.getRef(this); + props.dispatchSetClipboard(this); } - init(annotation) { - this.annotation = annotation; + init() { } hasCopy() { return this.state.copy != null; } copy(isAll) { if (isAll === null) { - isAll = this.annotation.getTarget() == null; + isAll = this.props.annotation.getTarget() == null; } - const copy = this.annotation.copyLabels(isAll); + const copy = this.props.annotation.copyLabels(isAll); if (copy.length === 0) { return; } @@ -33,7 +34,7 @@ class Clipboard extends React.Component { } paste() { const copy = this.state.copy; - this.annotation.pasteLabels(copy); + this.props.annotation.pasteLabels(copy); } render() { @@ -46,5 +47,18 @@ class Clipboard extends React.Component { ); } } -export default Clipboard; +const mapStateToProps = state => ({ + labelTool: state.tool.labelTool, + controls: state.tool.controls, + annotation: state.tool.annotation, +}); +const mapDispatchToProps = dispatch => ({ + dispatchSetClipboard: target => dispatch(setClipboard(target)) +}); +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ) +)(Clipboard); diff --git a/front/app/labeling_tool/controls.jsx b/front/app/labeling_tool/controls.jsx index 90bb9275..c8aaf6c3 100644 --- a/front/app/labeling_tool/controls.jsx +++ b/front/app/labeling_tool/controls.jsx @@ -9,7 +9,11 @@ import Divider from '@material-ui/core/Divider'; import Grid from '@material-ui/core/Grid'; import Button from '@material-ui/core/Button'; import IconButton from '@material-ui/core/IconButton'; -import {NavigateNext, NavigateBefore} from '@material-ui/icons'; +import { NavigateNext, NavigateBefore, ExitToApp } from '@material-ui/icons'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; + +import { setControls } from './actions/tool_action'; import KlassSet from 'automan/labeling_tool/klass_set'; import Annotation from 'automan/labeling_tool/annotation'; @@ -19,26 +23,16 @@ import Clipboard from 'automan/labeling_tool/clipboard'; import ImageLabelTool from 'automan/labeling_tool/image_label_tool'; import PCDLabelTool from 'automan/labeling_tool/pcd_label_tool'; + import {toolStyle, appBarHeight, drawerWidth} from 'automan/labeling_tool/tool-style'; import RequestClient from 'automan/services/request-client' class Controls extends React.Component { - labelTool = null; - annotation = null; - getAnnotation = (tgt) => { this.annotation = tgt; } - klassSet = null; - getKlassSet = (tgt) => { this.klassSet = tgt; } - history = null; - getHistory = (tgt) => { this.history = tgt; } - clipboard = null; - getClipboard = (tgt) => { this.clipboard = tgt; } - // progress frameLength = 0; // tool status - tools = []; toolNames = []; toolComponents = []; @@ -49,9 +43,8 @@ class Controls extends React.Component { skipFrameCount: 1, activeTool: 0 }; - this.labelTool = props.labelTool; - this.frameLength = this.labelTool.frameLength; + this.frameLength = props.labelTool.frameLength; this.mainContent = React.createRef(); this.initTools(); } @@ -67,35 +60,29 @@ class Controls extends React.Component { names: ['2D', '3D'] } }; - const type = LABEL_TYPES[this.labelTool.labelType]; + const type = LABEL_TYPES[this.props.labelTool.labelType]; if (type == null) { - console.error('Tool type error [' + this.labelTool.labelType + ']'); + console.error('Tool type error [' + this.props.labelTool.labelType + ']'); return; } this.toolNames = type.names; - this.toolComponents = []; - this.tools = type.tools.map((tool, idx) => { - const ref = React.createRef(); + this.toolComponents = type.tools.map((tool, idx) => { const Component = tool; const component = ( ); - this.toolComponents.push(component); - return ref; + return component; }); } init() { return Promise.all([ - this.annotation.init(this.klassSet, this.history), - this.klassSet.init(), - this.history.init(this.annotation), - this.clipboard.init(this.annotation) - + this.props.annotation.init(), + this.props.klassSet.init(), + this.props.history.init(), + this.props.clipboard.init() ]); } resize() { @@ -109,8 +96,8 @@ class Controls extends React.Component { if (this.mainContent.current != null) { this.mainContent.current.style.height = size.height + 'px'; } - this.tools.forEach((tool) => { - tool.current.handles.resize(size); + this.props.tools.forEach((tool) => { + tool.handles.resize(size); }); } initEvent() { @@ -130,20 +117,20 @@ class Controls extends React.Component { // Z key if (e.ctrlKey) { if (e.shiftKey) { - this.history.redo(); + this.props.history.redo(); } else { - this.history.undo(); + this.props.history.undo(); } } } else if (e.keyCode == 67) { // C key if (e.ctrlKey) { - this.clipboard.copy(null); + this.props.clipboard.copy(null); } } else if (e.keyCode == 86) { // V key if (e.ctrlKey) { - this.clipboard.paste(); + this.props.clipboard.paste(); } } else { this.getTool().handles.keydown(e); @@ -159,14 +146,14 @@ class Controls extends React.Component { } selectKlass(kls) { - if (!this.labelTool.isLoaded()) { + if (!this.props.labelTool.isLoaded()) { return false; } - let newKls = this.klassSet.setTarget(kls); + let newKls = this.props.klassSet.setTarget(kls); if (newKls !== null) { - const label = this.annotation.getTarget(); + const label = this.props.annotation.getTarget(); if (label !== null) { - this.annotation.changeKlass(label, newKls); + this.props.annotation.changeKlass(label, newKls); // update ?? } } else { @@ -176,20 +163,20 @@ class Controls extends React.Component { // ********* } getTargetKlass() { - return this.klassSet.getTarget(); + return this.props.klassSet.getTarget(); } getKlass(name) { - return this.klassSet.getByName(name); + return this.props.klassSet.getByName(name); } selectLabel(label) { - if (!this.labelTool.isLoaded()) { + if (!this.props.labelTool.isLoaded()) { return false; } - const oldLabel = this.annotation.getTarget() + const oldLabel = this.props.annotation.getTarget() let newLabel; - newLabel = this.annotation.setTarget(label); + newLabel = this.props.annotation.setTarget(label); if (newLabel !== null) { - this.klassSet.setTarget(newLabel.klass); + this.props.klassSet.setTarget(newLabel.klass); } //update this.getTool().updateTarget(oldLabel, newLabel); @@ -197,32 +184,32 @@ class Controls extends React.Component { // ********* } getTargetLabel() { - return this.annotation.getTarget(); + return this.props.annotation.getTarget(); } createLabel(klass, param) { - if (!this.labelTool.isLoaded()) { + if (!this.props.labelTool.isLoaded()) { return null; } let newLabel = null; try { - newLabel = this.annotation.create(klass, param); + newLabel = this.props.annotation.create(klass, param); } catch (e) { // error console.log(e); return null; } - this.annotation.setTarget(newLabel); - this.klassSet.setTarget(newLabel.klass); + this.props.annotation.setTarget(newLabel); + this.props.klassSet.setTarget(newLabel.klass); // update ?? return newLabel; // ********* } removeLabel(label) { - if (!this.labelTool.isLoaded()) { + if (!this.props.labelTool.isLoaded()) { return false; } try { - this.annotation.remove(label); + this.props.annotation.remove(label); } catch (e) { // error return false; @@ -233,15 +220,15 @@ class Controls extends React.Component { } getTools() { - return this.tools.map(ref => ref.current); + return this.props.tools; } setTool(idx) { const activeTool = this.state.activeTool if (activeTool === idx) { return; } - const prevTool = this.tools[activeTool].current; - const nextTool = this.tools[idx].current; + const prevTool = this.props.tools[activeTool]; + const nextTool = this.props.tools[idx]; this.setState({activeTool: idx}); prevTool.setActive(false); nextTool.setActive(true); @@ -249,7 +236,7 @@ class Controls extends React.Component { // ********* } getTool() { - return this.tools[this.state.activeTool].current; + return this.props.tools[this.state.activeTool]; } getToolFromCandidateId(id) { const filtered = this.getTools().filter(tool => @@ -296,11 +283,11 @@ class Controls extends React.Component { } let savePromise; - if (this.annotation.isChanged()) { + if (this.props.annotation.isChanged()) { const TEXT_SAVE = 'Do you want to save?'; const TEXT_MOVE = 'Do you want to move frame WITHOUT saving?'; if ( window.confirm(TEXT_SAVE) ) { - savePromise = this.annotation.save(); + savePromise = this.props.annotation.save(); } else if ( window.confirm(TEXT_MOVE) ) { savePromise = Promise.resolve(); } else { @@ -315,14 +302,15 @@ class Controls extends React.Component { .then( () => { }, - () => { + (err) => { + console.error(err); } ); return true; } saveFrame() { - return this.annotation.save() + return this.props.annotation.save() .then(() => this.reloadFrame()); } reloadFrame() { @@ -339,9 +327,9 @@ class Controls extends React.Component { } this.isLoading = true; - return this.labelTool.loadBlobURL(num) + return this.props.labelTool.loadBlobURL(num) .then(() => { - return this.annotation.load(num); + return this.props.annotation.load(num); }) .then(() => { return Promise.all( @@ -360,29 +348,65 @@ class Controls extends React.Component { } - componentDidMount() { - this.labelTool.candidateInfo.forEach(info => { - this.getTools().forEach(tool => { - if (tool.dataType === info.data_type) { - if (tool.candidateId >= 0) { - return; + initialized = false; + isAllComponentsReady() { + return !this.initialized && + this.props.controls != null && + this.props.annotation != null && + this.props.klassSet != null && + this.props.history != null && + this.props.clipboard != null; + } + toolInitialized = false; + isToolReady() { + return !this.toolInitialized && + this.props.controls == null && + this.props.toolsCnt == this.toolComponents.length; + } + componentDidMount() { } + componentDidUpdate(prevProps, prevState) { + if (this.isToolReady()) { + this.props.labelTool.candidateInfo.forEach(info => { + this.getTools().forEach(tool => { + if (tool.dataType === info.data_type) { + if (tool.candidateId >= 0) { + return; + } + tool.candidateId = info.candidate_id; // TODO: multi candidate_id + this.props.labelTool.filenames[tool.candidateId] = []; } - tool.candidateId = info.candidate_id; // TODO: multi candidate_id - this.labelTool.filenames[tool.candidateId] = []; - } + }); }); - }); - - this.tools[this.state.activeTool].current.setActive(true); - this.resize(); + this.props.tools[this.state.activeTool].setActive(true); - this.init().then(() => { - this.props.onload(this); - }); + this.resize(); + + this.toolInitialized = true; + this.props.dispatchSetControls(this); + } + + if (this.isAllComponentsReady()) { + this.init().then(() => { + this.initialized = true; + this.props.onload(this); + }); + } } // events + onClickLogout = (e) => { + this.isLoading = true; + RequestClient.delete( + this.props.labelTool.getURL('unlock'), + null, + res => { + window.close(); + }, + err => { + } + ); + }; onClickNextFrame = (e) => { this.nextFrame(); }; @@ -416,20 +440,14 @@ class Controls extends React.Component { renderKlassSet(classes) { return ( ); } renderLabels(classes) { return ( ); } @@ -477,13 +495,11 @@ class Controls extends React.Component { @@ -500,6 +516,22 @@ class Controls extends React.Component { ); } + renderRightBar(classes) { + const tool = this.getTool(); + const editor = tool == null ? null : tool.getEditor(); + return ( + + {editor} + + ); + } render() { const classes = this.props.classes; let skip = this.state.skipFrameCount; @@ -550,27 +582,23 @@ class Controls extends React.Component { {frameNumberForm} - + {this.renderKlassSet(classes)} + + + + + ); - // TODO: show bbox variable & edit it - let editBar = ( - - - ); return ( -
+
{appBar} {this.renderLeftBar(classes)}
{this.toolComponents}
- {editBar} + {this.renderRightBar(classes)}
); } } -export default withStyles(toolStyle)(Controls); +const mapStateToProps = state => ({ + controls: state.tool.controls, + annotation: state.tool.annotation, + klassSet: state.tool.klassSet, + history: state.tool.history, + clipboard: state.tool.clipboard, + tools: state.tool.tools, + toolsCnt: state.tool.toolsCnt +}); +const mapDispatchToProps = dispatch => ({ + dispatchSetControls: target => dispatch(setControls(target)) +}); +export default compose( + withStyles(toolStyle), + connect( + mapStateToProps, + mapDispatchToProps + ) +)(Controls); diff --git a/front/app/labeling_tool/history.jsx b/front/app/labeling_tool/history.jsx index 886c9136..0461ab18 100644 --- a/front/app/labeling_tool/history.jsx +++ b/front/app/labeling_tool/history.jsx @@ -3,23 +3,23 @@ import ReactDOM from 'react-dom'; import Grid from '@material-ui/core/Grid'; import Button from '@material-ui/core/Button'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; + +import { setHistory } from './actions/tool_action'; class History extends React.Component { // history - constructor(props) { super(props); - this.annotation = null; - this._controls = props.controls; this.tmpHist = null; this.state = { undoHistory: [], redoHistory: [] }; - props.getRef(this); + props.dispatchSetHistory(this); } - init(annotation) { - this.annotation = annotation; + init() { } hasUndo() { return this.state.undoHistory.length > 0; @@ -38,7 +38,7 @@ class History extends React.Component { }; if (hist.type == 'change') { ret.objects = hist.objects.map(obj => { - const label = this.annotation.getLabel(obj.id); + const label = this.props.annotation.getLabel(obj.id); return label.toHistory(); }); } else if (hist.type === 'create') { @@ -53,15 +53,15 @@ class History extends React.Component { undoHist(hist) { if (hist.type === 'change') { for (let obj of hist.objects) { - const label = this.annotation.getLabel(obj.id); + const label = this.props.annotation.getLabel(obj.id); label.fromHistory(obj); } - this._controls.selectLabel(hist.objects[0].id); + this.props.controls.selectLabel(hist.objects[0].id); } else if (hist.type === 'create') { - this.annotation.removeFromHistory(hist.objects); + this.props.annotation.removeFromHistory(hist.objects); } else if (hist.type === 'delete') { - const labels = this.annotation.createFromHistory(hist.objects); - this._controls.selectLabel(labels[0]); + const labels = this.props.annotation.createFromHistory(hist.objects); + this.props.controls.selectLabel(labels[0]); } else { // error } @@ -166,5 +166,18 @@ class History extends React.Component { ); } } -export default History; +const mapStateToProps = state => ({ + labelTool: state.tool.labelTool, + controls: state.tool.controls, + annotation: state.tool.annotation, +}); +const mapDispatchToProps = dispatch => ({ + dispatchSetHistory: target => dispatch(setHistory(target)) +}); +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ) +)(History); diff --git a/front/app/labeling_tool/image_label_tool.jsx b/front/app/labeling_tool/image_label_tool.jsx index 14133155..067a1705 100644 --- a/front/app/labeling_tool/image_label_tool.jsx +++ b/front/app/labeling_tool/image_label_tool.jsx @@ -2,15 +2,23 @@ import React from 'react'; import ReactDOM from 'react-dom'; -export default class ImageLabelTool extends React.Component { +import { compose } from 'redux'; +import { connect } from 'react-redux'; + +import { addTool } from './actions/tool_action'; + +import ImageBBox from './image_tool/image_bbox'; + +class ImageLabelTool extends React.Component { constructor(props) { super(props); this.state = { scale: 1.0 }; - this._labelTool = props.labelTool; - this._controls = props.controls; this._element = React.createRef(); + this._wipeElement = React.createRef(); + this._wrapperElement = React.createRef(); + props.dispatchAddTool(props.idx, this); } componentDidMount() { this.init(); @@ -18,29 +26,73 @@ export default class ImageLabelTool extends React.Component { getButtons() { return null; } + getEditor() { + return null; + } render() { - const wrapperStyle = { + const mainStyle = { transform: `scale(${this.state.scale})`, transformOrigin: 'left top' }; + const wipeScale = this.state.scale * 0.32; + const wipeStyle = { + position: 'absolute', + transform: `scale(${wipeScale})`, + transformOrigin: 'left bottom', + bottom: 0, + left: 0, + borderTop: 'solid 2px #fff', + borderRight: 'solid 2px #fff' + }; + const wipeTextStyle = { + transform: `scale(${1 / wipeScale})`, + transformOrigin: 'left top', + position: 'absolute', + top: 0, + left: 0, + backgroundColor: '#fff', + padding: '0px 7px' + } return (
+ ref={this._wrapperElement} + style={{ + position: 'relative', + height: '100%' + }} + > +
+
this.props.controls.previousFrame()} + > +
+ Prev +
+
+
); } // private - _controls = null; - _labelTool = null; _decoration = null; // DOM + _wrapperElement = null; _element = null; + _wipeElement = null; + _wrapper = null; _container = null; _paper = null; + _wipePaper = null; _loaded = true; _image = null; + _wipeImage = null; _imageSize = { width: 0, height: 0 @@ -67,14 +119,24 @@ export default class ImageLabelTool extends React.Component { this._paper = Raphael(container); this._container = $(container); this._decoration = new Decorations(this._paper); - this._container.hide(); + const wipe = this._wipeElement.current; + this._wipePaper = Raphael(wipe); + const wrapper = $(this._wrapperElement.current); + wrapper.hide(); + this._wrapper = wrapper; } load(frame) { this._loaded = false; // TODO: use getURL - const imgURL = this._labelTool.getURL('frame_blob', this.candidateId, frame); + const imgURL = this.props.labelTool.getURL('frame_blob', this.candidateId, frame); this._initImage(imgURL); + if (frame >= 1) { + const wipeImgURL = this.props.labelTool.getURL('frame_blob', this.candidateId, frame - 1); + this._initWipeImage(wipeImgURL); + } else { + this._initWipeImage(null); + } this._decoration.hide(); return new Promise((resolve, reject) => { @@ -94,17 +156,37 @@ export default class ImageLabelTool extends React.Component { }; setActive(isActive) { if ( isActive ) { - this._container.show(); + this._wrapper.show(); } else { - this._container.hide(); + this._wrapper.hide(); } - // set canvas size - - // over? + } + createWipeBBox(content, klass) { + // TODO: add class + const box = ImageBBox.fromContentToObj(content).box; + const size = box.getSize(); + const rect = this._wipePaper.rect( + box.min.x, box.min.y, + size.x, size.y + ).attr({ + 'stroke-width': 1, + 'stroke': klass.getColor() + }); + return { + rect: rect, + select(flag) { + this.rect.attr({ + 'stroke-width': flag ? 5 : 2 + }); + } + }; } createBBox(content) { return new ImageBBox(this, content); } + disposeWipeBBox(bbox) { + bbox.rect.remove(); + } disposeBBox(bbox) { bbox.remove(); } @@ -130,17 +212,37 @@ export default class ImageLabelTool extends React.Component { _resize() { const paper = this._paper; + const wipePaper = this._wipePaper; let scale = Math.min( this._wrapperSize.width / this._imageSize.width, this._wrapperSize.height / this._imageSize.height ); paper.setSize( - this._imageSize.width, + this._imageSize.width, + this._imageSize.height + ); + wipePaper.setSize( + this._imageSize.width, this._imageSize.height ); this.setState({ scale: scale }); } + _initWipeImage(url) { + if (this._wipeImage != null) { + this._wipeImage.remove(); + this._wipeImage = null; + } + if (url == null) { + this._wipeElement.current.style.display = 'none'; + return; + } + this._wipeElement.current.style.display = ''; + const paper = this._wipePaper; + const image = paper.image(url, 0, 0, "100%", "100%"); + image.toBack(); + this._wipeImage = image; + } _initImage(url) { if (this._image != null) { this._image.remove(); @@ -163,7 +265,6 @@ export default class ImageLabelTool extends React.Component { image.attr({ 'cursor': 'crosshair' }); - image.toBack(); image.drag( this._imageDragMove, this._imageDragStart, @@ -175,7 +276,7 @@ export default class ImageLabelTool extends React.Component { _creatingRect = null; _creatingBox = null; _imageDragMove = (dx, dy) => { - const klass = this._controls.getTargetKlass(); + const klass = this.props.controls.getTargetKlass(); dx = dx / this.state.scale | 0; dy = dy / this.state.scale | 0; if (this._creatingRect == null) { @@ -208,7 +309,7 @@ export default class ImageLabelTool extends React.Component { }; _imageDragStart = (x, y, e) => { if (e.button !== 0) { return; } // not left click - this._controls.selectLabel(null); + this.props.controls.selectLabel(null); const offset = this._container.offset(); this._creatingBox = { sx: (x - offset.left) / this.state.scale | 0, @@ -227,8 +328,8 @@ export default class ImageLabelTool extends React.Component { 'max_y_2d': Math.max(box.sy, box.ey) }); // TODO: add branch use selecting label - const label = this._controls.createLabel( - this._controls.getTargetKlass(), + const label = this.props.controls.createLabel( + this.props.controls.getTargetKlass(), {[this.candidateId]: imageBBox} ); if (label == null) { @@ -311,9 +412,6 @@ class Decorations { }; - - - // BBox const BBoxParams = { attrs: { @@ -338,217 +436,20 @@ const BBoxParams = { ], }, }; -class ImageBBox { - constructor(imageTool, content) { - this.imageTool = imageTool; - this.paper = imageTool._paper; - this.deco = imageTool._decoration; - this.label = null; - this.selected = false; - this.box = new THREE.Box2( - new THREE.Vector2(0, 0), - new THREE.Vector2(0, 0) - ); - this.prev = null; - if (content != null) { - // TODO: is there no annotation? - this.fromContent(content); - } - this.initRect(); - this.initResizer(); - } - getScale() { - return this.imageTool.state.scale; - } - setLabel(label) { - if (this.label != null) { - // TODO: control error - throw "Label already set"; - } - this.label = label; - //this.labelItem = label.addBBox('Image'); - this.rect.attr({ 'stroke': label.getColor() }); - } - updateKlass() { - this.rect.attr({ 'stroke': this.label.getColor() }); - this.deco.show(this); - } - updateParam() { - // TODO: change content - this.setElementPos(); - } - remove() { - //this.labelItem.remove(); - this.rect.remove(); - this.edgeResizers.forEach(it => it.remove()); - this.cornerResizers.forEach(it => it.remove()); - } - toContent(obj) { - obj['min_x_2d'] = this.box.min.x; - obj['min_y_2d'] = this.box.min.y; - obj['max_x_2d'] = this.box.max.x; - obj['max_y_2d'] = this.box.max.y; - } - fromContent(content) { - this.box.set(new THREE.Vector2( - +content['min_x_2d'], - +content['min_y_2d'] - ), new THREE.Vector2( - +content['max_x_2d'], - +content['max_y_2d'] - ) - ); - } - initRect() { - const size = this.box.getSize(); - const rect = this.paper.rect( - this.box.min.x, this.box.min.y, - size.x, size.y - ).attr(BBoxParams.attrs.rect); - // 4th arguments set 'this' into callbacks - rect.drag(this.dragMove, this.dragStart, this.dragEnd, this); - this.rect = rect; - } - initResizer() { - const paper = this.paper; - const edges = [ - // left, right, top, bottom - paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[0]) - .drag(this.resizerDragL, this.dragStart, this.dragEnd, this), - paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[0]) - .drag(this.resizerDragR, this.dragStart, this.dragEnd, this), - paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[1]) - .drag(this.resizerDragT, this.dragStart, this.dragEnd, this), - paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[1]) - .drag(this.resizerDragB, this.dragStart, this.dragEnd, this) - ], corners = [ - // tl, tr, bl, br - paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[0]) - .drag(this.resizerDragTL, this.dragStart, this.dragEnd, this), - paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[1]) - .drag(this.resizerDragTR, this.dragStart, this.dragEnd, this), - paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[1]) - .drag(this.resizerDragBL, this.dragStart, this.dragEnd, this), - paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[0]) - .drag(this.resizerDragBR, this.dragStart, this.dragEnd, this) - ]; - edges.forEach(it => it.attr(BBoxParams.attrs.resizer)); - corners.forEach(it => it.attr(BBoxParams.attrs.resizer)); - this.edgeResizers = edges; - this.cornerResizers = corners; - } - resizerDragL(dx, dy) { this.setMinX(dx); this.setVisiblePos(); } - resizerDragR(dx, dy) { this.setMaxX(dx); this.setVisiblePos(); } - resizerDragT(dx, dy) { this.setMinY(dy); this.setVisiblePos(); } - resizerDragB(dx, dy) { this.setMaxY(dy); this.setVisiblePos(); } - resizerDragTL(dx, dy) { this.setMinX(dx); this.setMinY(dy); this.setVisiblePos(); } - resizerDragTR(dx, dy) { this.setMaxX(dx); this.setMinY(dy); this.setVisiblePos(); } - resizerDragBL(dx, dy) { this.setMinX(dx); this.setMaxY(dy); this.setVisiblePos(); } - resizerDragBR(dx, dy) { this.setMaxX(dx); this.setMaxY(dy); this.setVisiblePos(); } - setMinX(dx) { - dx = dx / this.getScale() | 0; - const prevBox = this.prev.box; - const x = Math.min(Math.max(prevBox.min.x+dx, 0), - prevBox.max.x - this.label.getMinSize().x); - this.box.min.x = x; - } - setMinY(dy) { - dy = dy / this.getScale() | 0; - const prevBox = this.prev.box; - const y = Math.min(Math.max(prevBox.min.y+dy, 0), - prevBox.max.y - this.label.getMinSize().y); - this.box.min.y = y; - } - setMaxX(dx) { - dx = dx / this.getScale() | 0; - const prevBox = this.prev.box; - const width = this.paper.width; - const x = Math.max(Math.min(prevBox.max.x+dx, width), - prevBox.min.x + this.label.getMinSize().x); - this.box.max.x = x; - } - setMaxY(dy) { - dy = dy / this.getScale() | 0; - const prevBox = this.prev.box; - const height = this.paper.height; - const y = Math.max(Math.min(prevBox.max.y+dy, height), - prevBox.min.y + this.label.getMinSize().y); - this.box.max.y = y; - } - toFront() { - this.rect.toFront(); - this.edgeResizers.forEach(it => it.toFront()); - this.cornerResizers.forEach(it => it.toFront()); - } - dragMove(dx, dy) { - dx = dx / this.getScale() | 0; - dy = dy / this.getScale() | 0; - const prev = this.prev; - const width = this.paper.width, - height = this.paper.height; - const rdx = Math.min(width - prev.box.max.x, - Math.max(dx, -prev.box.min.x)); - const rdy = Math.min(height- prev.box.max.y, - Math.max(dy, -prev.box.min.y)); - this.box = prev.box.clone().translate(new THREE.Vector2(rdx, rdy)); - this.setVisiblePos(); - } - dragStart() { - this.imageTool._controls.selectLabel(this.label); - this.prev = { - box: this.box.clone() - }; - this.label.createHistory(); - } - dragEnd() { - if (!this.box.equals(this.prev.box)) { - this.label.addHistory(); - } - this.setElementPos(); - } - setVisiblePos() { - const l = this.box.min.x, t = this.box.min.y, - size = this.box.getSize(); - setRectPos(this.rect, l, t, size.x, size.y); - if ( this.selected ) { - this.deco.move(this); - } - } - setElementPos() { - if (this.prev!=null && !this.label.isChanged) { - this.label.isChanged = !this.box.equals(this.prev.box); - } - this.prev = null; - const l = this.box.min.x, t = this.box.min.y, - r = this.box.max.x, b = this.box.max.y, - size = this.box.getSize(), - d = 3, d2 = d*2; - setRectPos(this.rect, l, t, size.x, size.y); - // left, right, top, bottom - setRectPos(this.edgeResizers[0], l-d, t+d, d2, size.y-d2); - setRectPos(this.edgeResizers[1], r-d, t+d, d2, size.y-d2); - setRectPos(this.edgeResizers[2], l+d, t-d, size.x-d2, d2); - setRectPos(this.edgeResizers[3], l+d, b-d, size.x-d2, d2); - // tl, tr, bl, br - setRectPos(this.cornerResizers[0], l-d, t-d, d2, d2); - setRectPos(this.cornerResizers[1], r-d, t-d, d2, d2); - setRectPos(this.cornerResizers[2], l-d, b-d, d2, d2); - setRectPos(this.cornerResizers[3], r-d, b-d, d2, d2); - if ( this.selected ) { - this.deco.move(this); - } - } -} -const setRectPos = function(elem, x, y, w, h) { - elem.attr({x:x, y:y, width:w, height:h}); -}; - - - - - - +const mapStateToProps = state => ({ + controls: state.tool.controls, + labelTool: state.tool.labelTool +}); +const mapDispatchToProps = dispatch => ({ + dispatchAddTool: (idx, target) => dispatch(addTool(idx, target)) +}); +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ) +)(ImageLabelTool); diff --git a/front/app/labeling_tool/image_tool/image_bbox.js b/front/app/labeling_tool/image_tool/image_bbox.js new file mode 100644 index 00000000..0cd4eb4b --- /dev/null +++ b/front/app/labeling_tool/image_tool/image_bbox.js @@ -0,0 +1,247 @@ + +// BBox +const BBoxParams = { + attrs: { + rect: { + 'stroke-width': 2, + 'fill': '#fff', + 'fill-opacity': 0, + 'cursor': 'all-scroll' + }, + resizer: { + 'stroke': 'none', + 'fill': '#fff', + 'fill-opacity': 0 + }, + edges: [ + {'cursor': 'ew-resize'}, + {'cursor': 'ns-resize'} + ], + corners: [ + {'cursor': 'nwse-resize'}, + {'cursor': 'nesw-resize'} + ], + }, +}; +export default class ImageBBox { + constructor(imageTool, content) { + this.imageTool = imageTool; + this.paper = imageTool._paper; + this.deco = imageTool._decoration; + this.label = null; + this.selected = false; + this.box = new THREE.Box2( + new THREE.Vector2(0, 0), + new THREE.Vector2(0, 0) + ); + this.prev = null; + if (content != null) { + // TODO: is there no annotation? + this.fromContent(content); + } + this.initRect(); + this.initResizer(); + } + getScale() { + return this.imageTool.state.scale; + } + setLabel(label) { + if (this.label != null) { + // TODO: control error + throw "Label already set"; + } + this.label = label; + //this.labelItem = label.addBBox('Image'); + this.rect.attr({ 'stroke': label.getColor() }); + } + updateKlass() { + this.rect.attr({ 'stroke': this.label.getColor() }); + this.deco.show(this); + } + updateParam() { + // TODO: change content + this.setElementPos(); + } + remove() { + //this.labelItem.remove(); + this.rect.remove(); + this.edgeResizers.forEach(it => it.remove()); + this.cornerResizers.forEach(it => it.remove()); + } + toContent(obj) { + obj['min_x_2d'] = this.box.min.x; + obj['min_y_2d'] = this.box.min.y; + obj['max_x_2d'] = this.box.max.x; + obj['max_y_2d'] = this.box.max.y; + } + static fromContentToObj(content) { + const ret = { + box: new THREE.Box2( + new THREE.Vector2( + +content['min_x_2d'], + +content['min_y_2d'] + ), + new THREE.Vector2( + +content['max_x_2d'], + +content['max_y_2d'] + ) + ) + }; + return ret; + } + fromContent(content) { + this.box.set( + new THREE.Vector2( + +content['min_x_2d'], + +content['min_y_2d'] + ), + new THREE.Vector2( + +content['max_x_2d'], + +content['max_y_2d'] + ) + ); + } + initRect() { + const size = this.box.getSize(); + const rect = this.paper.rect( + this.box.min.x, this.box.min.y, + size.x, size.y + ).attr(BBoxParams.attrs.rect); + // 4th arguments set 'this' into callbacks + rect.drag(this.dragMove, this.dragStart, this.dragEnd, this); + this.rect = rect; + } + initResizer() { + const paper = this.paper; + const edges = [ + // left, right, top, bottom + paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[0]) + .drag(this.resizerDragL, this.dragStart, this.dragEnd, this), + paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[0]) + .drag(this.resizerDragR, this.dragStart, this.dragEnd, this), + paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[1]) + .drag(this.resizerDragT, this.dragStart, this.dragEnd, this), + paper.rect(0,0,0,0).attr(BBoxParams.attrs.edges[1]) + .drag(this.resizerDragB, this.dragStart, this.dragEnd, this) + ], corners = [ + // tl, tr, bl, br + paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[0]) + .drag(this.resizerDragTL, this.dragStart, this.dragEnd, this), + paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[1]) + .drag(this.resizerDragTR, this.dragStart, this.dragEnd, this), + paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[1]) + .drag(this.resizerDragBL, this.dragStart, this.dragEnd, this), + paper.rect(0,0,0,0).attr(BBoxParams.attrs.corners[0]) + .drag(this.resizerDragBR, this.dragStart, this.dragEnd, this) + ]; + edges.forEach(it => it.attr(BBoxParams.attrs.resizer)); + corners.forEach(it => it.attr(BBoxParams.attrs.resizer)); + this.edgeResizers = edges; + this.cornerResizers = corners; + } + resizerDragL(dx, dy) { this.setMinX(dx); this.setVisiblePos(); } + resizerDragR(dx, dy) { this.setMaxX(dx); this.setVisiblePos(); } + resizerDragT(dx, dy) { this.setMinY(dy); this.setVisiblePos(); } + resizerDragB(dx, dy) { this.setMaxY(dy); this.setVisiblePos(); } + resizerDragTL(dx, dy) { this.setMinX(dx); this.setMinY(dy); this.setVisiblePos(); } + resizerDragTR(dx, dy) { this.setMaxX(dx); this.setMinY(dy); this.setVisiblePos(); } + resizerDragBL(dx, dy) { this.setMinX(dx); this.setMaxY(dy); this.setVisiblePos(); } + resizerDragBR(dx, dy) { this.setMaxX(dx); this.setMaxY(dy); this.setVisiblePos(); } + setMinX(dx) { + dx = dx / this.getScale() | 0; + const prevBox = this.prev.box; + const x = Math.min(Math.max(prevBox.min.x+dx, 0), + prevBox.max.x - this.label.getMinSize().x); + this.box.min.x = x; + } + setMinY(dy) { + dy = dy / this.getScale() | 0; + const prevBox = this.prev.box; + const y = Math.min(Math.max(prevBox.min.y+dy, 0), + prevBox.max.y - this.label.getMinSize().y); + this.box.min.y = y; + } + setMaxX(dx) { + dx = dx / this.getScale() | 0; + const prevBox = this.prev.box; + const width = this.paper.width; + const x = Math.max(Math.min(prevBox.max.x+dx, width), + prevBox.min.x + this.label.getMinSize().x); + this.box.max.x = x; + } + setMaxY(dy) { + dy = dy / this.getScale() | 0; + const prevBox = this.prev.box; + const height = this.paper.height; + const y = Math.max(Math.min(prevBox.max.y+dy, height), + prevBox.min.y + this.label.getMinSize().y); + this.box.max.y = y; + } + toFront() { + this.rect.toFront(); + this.edgeResizers.forEach(it => it.toFront()); + this.cornerResizers.forEach(it => it.toFront()); + } + dragMove(dx, dy) { + dx = dx / this.getScale() | 0; + dy = dy / this.getScale() | 0; + const prev = this.prev; + const width = this.paper.width, + height = this.paper.height; + const rdx = Math.min(width - prev.box.max.x, + Math.max(dx, -prev.box.min.x)); + const rdy = Math.min(height- prev.box.max.y, + Math.max(dy, -prev.box.min.y)); + this.box = prev.box.clone().translate(new THREE.Vector2(rdx, rdy)); + this.setVisiblePos(); + } + dragStart() { + this.imageTool.props.controls.selectLabel(this.label); + this.prev = { + box: this.box.clone() + }; + this.label.createHistory(); + } + dragEnd() { + if (!this.box.equals(this.prev.box)) { + this.label.addHistory(); + } + this.setElementPos(); + } + setVisiblePos() { + const l = this.box.min.x, t = this.box.min.y, + size = this.box.getSize(); + setRectPos(this.rect, l, t, size.x, size.y); + if ( this.selected ) { + this.deco.move(this); + } + } + setElementPos() { + if (this.prev!=null && !this.label.isChanged) { + this.label.isChanged = !this.box.equals(this.prev.box); + } + this.prev = null; + const l = this.box.min.x, t = this.box.min.y, + r = this.box.max.x, b = this.box.max.y, + size = this.box.getSize(), + d = 3, d2 = d*2; + setRectPos(this.rect, l, t, size.x, size.y); + // left, right, top, bottom + setRectPos(this.edgeResizers[0], l-d, t+d, d2, size.y-d2); + setRectPos(this.edgeResizers[1], r-d, t+d, d2, size.y-d2); + setRectPos(this.edgeResizers[2], l+d, t-d, size.x-d2, d2); + setRectPos(this.edgeResizers[3], l+d, b-d, size.x-d2, d2); + // tl, tr, bl, br + setRectPos(this.cornerResizers[0], l-d, t-d, d2, d2); + setRectPos(this.cornerResizers[1], r-d, t-d, d2, d2); + setRectPos(this.cornerResizers[2], l-d, b-d, d2, d2); + setRectPos(this.cornerResizers[3], r-d, b-d, d2, d2); + if ( this.selected ) { + this.deco.move(this); + } + } +} +const setRectPos = function(elem, x, y, w, h) { + elem.attr({x:x, y:y, width:w, height:h}); +}; + diff --git a/front/app/labeling_tool/klass_set.js b/front/app/labeling_tool/klass_set.js index 7d80c39a..6fd71453 100644 --- a/front/app/labeling_tool/klass_set.js +++ b/front/app/labeling_tool/klass_set.js @@ -2,17 +2,15 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { withStyles } from '@material-ui/core/styles'; -//import List from '@material-ui/core/List'; -//import ListItem from '@material-ui/core/ListItem'; -//import ListItemIcon from '@material-ui/core/ListItemIcon'; -//import ListItemText from '@material-ui/core/ListItemText'; -//import ListSubheader from '@material-ui/core/ListSubheader'; - import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; import classNames from 'classnames'; +import { setKlassSet } from './actions/tool_action'; + const KlassTab = withStyles(theme => ({ root: { '&:hover': { @@ -22,8 +20,6 @@ const KlassTab = withStyles(theme => ({ }))(props => ); class KlassSet extends React.Component { - _labelTool = null; - _controls = null; // data _klasses = new Map(); _klassList = []; @@ -32,17 +28,15 @@ class KlassSet extends React.Component { constructor(props) { super(props); - this._labelTool = props.labelTool; - this._controls = props.controls; this.state = { targetKlass: null, targetIndex: -1 }; - props.getRef(this); + props.dispatchSetKlassSet(this); } init() { return new Promise((resolve, reject) => { - let klassset = this._labelTool.getProjectInfo().klassset; + let klassset = this.props.labelTool.getProjectInfo().klassset; klassset.records.forEach((klass) => { let config = JSON.parse(klass.config); @@ -101,29 +95,6 @@ class KlassSet extends React.Component { } return null; } - /* - renderList(classes) { - let list = []; - for (let [_, klass] of this._klasses) { - const isSelected = klass === this.state.targetKlass; - list.push( - this._controls.selectKlass(klass)} - button - > -
- - - ); - } - return list; - } - */ renderTabs(classes) { let list = []; for (let klass of this._klassList) { @@ -146,7 +117,7 @@ class KlassSet extends React.Component { return list; } handleTabChange = (e, newVal) => { - this._controls.selectKlass(this._klassList[newVal]); + this.props.controls.selectKlass(this._klassList[newVal]); this.setState({ targetIndex: newVal }); }; render() { @@ -177,7 +148,19 @@ class KlassSet extends React.Component { */ } }; -export default KlassSet; +const mapStateToProps = state => ({ + labelTool: state.tool.labelTool, + controls: state.tool.controls, +}); +const mapDispatchToProps = dispatch => ({ + dispatchSetKlassSet: target => dispatch(setKlassSet(target)) +}); +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ) +)(KlassSet); class Klass { constructor(klassSet, id, name, color, size) { diff --git a/front/app/labeling_tool/label_tool.jsx b/front/app/labeling_tool/label_tool.jsx index c41d29a9..a8437699 100644 --- a/front/app/labeling_tool/label_tool.jsx +++ b/front/app/labeling_tool/label_tool.jsx @@ -1,13 +1,21 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { createStore, applyMiddleware } from 'redux'; +import { Provider } from 'react-redux'; +import thunk from 'redux-thunk'; -import LabelTool from 'automan/labeling_tool/base_label_tool' +import LabelTool from 'automan/labeling_tool/base_label_tool'; +import reducer from 'automan/labeling_tool/reducers/reducer'; +const store = createStore( + reducer, + applyMiddleware(thunk) +); ReactDOM.render( -
+ -
, + , document.getElementById('wrapper') ); diff --git a/front/app/labeling_tool/pcd_label_tool.jsx b/front/app/labeling_tool/pcd_label_tool.jsx index fd258990..cd5dba72 100644 --- a/front/app/labeling_tool/pcd_label_tool.jsx +++ b/front/app/labeling_tool/pcd_label_tool.jsx @@ -3,24 +3,30 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Button from '@material-ui/core/Button'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import { addTool } from './actions/tool_action'; + +import BoxFrameObject from './pcd_tool/box_frame_object'; +import PCDBBox from './pcd_tool/pcd_bbox'; +import EditBar from './pcd_tool/edit_bar'; // 3d eidt arrow const arrowColors = [0xff0000, 0x00ff00, 0x0000ff], - hoverColors = [0xffaaaa, 0xaaffaa, 0xaaaaff], AXES = [new THREE.Vector3(1,0,0), new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1)]; const ZERO2 = new THREE.Vector2(0, 0); const EDIT_OBJ_SIZE = 0.5; -export default class PCDLabelTool extends React.Component { +class PCDLabelTool extends React.Component { constructor(props) { super(props); this.state = { }; - this._labelTool = props.labelTool; - this._controls = props.controls; - this._element = React.createRef(); + this._wrapperElement = React.createRef(); + this._mainElement = React.createRef(); + this._wipeElement = React.createRef(); this._toolButtons = ( + + + + + +
+ ); + } +} +const mapStateToProps = state => ({ + targetLabel: state.annotation.targetLabel, +}); +export default compose( + connect( + mapStateToProps, + null + ) +)(PCDEditBar); + diff --git a/front/app/labeling_tool/pcd_tool/pcd_bbox.js b/front/app/labeling_tool/pcd_tool/pcd_bbox.js new file mode 100644 index 00000000..ea2d4fbf --- /dev/null +++ b/front/app/labeling_tool/pcd_tool/pcd_bbox.js @@ -0,0 +1,235 @@ +import BoxFrameObject from './box_frame_object'; + +const BBoxParams = { + geometry: new THREE.CubeGeometry(1.0, 1.0, 1.0), + material: new THREE.MeshBasicMaterial({ + color: 0x008866, + }) +}; +const ZERO2 = new THREE.Vector2(0, 0); +const EDIT_OBJ_SIZE = 0.5; + +export default class PCDBBox { + constructor(pcdTool, content) { + this.pcdTool = pcdTool; + this.label = null; + this.selected = false; + this.box = { + pos: new THREE.Vector3(0,0,0), + size: new THREE.Vector3(0,0,0), + yaw: 0 + }; + if (content != null) { + // init parameters + this.fromContent(content); + } + this.initCube(); + this.pcdTool.pcdBBoxes.add(this); + this.pcdTool.redrawRequest(); + } + setSize2(x, y) { + const res = this.setSize(x, y, this.box.size.z); + return new THREE.Vector2(res.x, res.y); + } + setSize2d(x, y) { + const prev = this.box.size.clone(); + const res = this.setSize(x, y, this.box.size.z); + const ret = new THREE.Vector2(res.x-prev.x, res.y-prev.y) + .rotateAround(ZERO2, this.box.yaw); + return ret; + } + setSizeZ(z) { + const prev = this.box.size.clone(); + const res = this.setSize(prev.x, prev.y, z); + return res.z - prev.z; + } + setSize(x, y, z) { + const minSize = new THREE.Vector3(0.1, 0.1, 0.1); + this.box.size.set(x, y, z).max(minSize); + return this.box.size.clone(); + } + setZ(center, height) { + const h = Math.max(height, 0.1); // use min size + this.box.size.z = h; + this.box.pos.z = center; + } + setLabel(label) { + if (this.label != null) { + // TODO: control error + throw "Label already set"; + } + this.label = label; + this.cube.meshFrame.setColor(label.getColor()); + } + updateSelected(selected) { + this.selected = selected; + this.cube.meshFrame.setStatus(selected, false); + this.cube.meshFrame.setBold(selected); + const box = this.box; + this.cube.meshFrame.setParam(box.pos, box.size, box.yaw); + } + hover(isInto) { + this.cube.meshFrame.setStatus(this.selected, isInto); + } + updateKlass() { + this.cube.meshFrame.setColor(this.label.getColor()); + } + updateParam() { + this.updateCube(true); + this.pcdTool.redrawRequest(); + } + remove() { + // TODO: remove meshes + this.cube.meshFrame.removeFrom(this.pcdTool._scene); + const group = this.cube.editGroup; + this.pcdTool._scene.remove(group); + this.pcdTool.redrawRequest(); + this.pcdTool.pcdBBoxes.delete(this); + } + toContent(obj) { + // make object values by parameters + obj['x_3d'] = this.box.pos.x; + obj['y_3d'] = this.box.pos.y; + obj['z_3d'] = this.box.pos.z; + obj['width_3d'] = this.box.size.x; + obj['height_3d'] = this.box.size.y; + obj['length_3d'] = this.box.size.z; + obj['rotation_y'] = this.box.yaw; + } + static fromContentToObj(content) { + const ret = { + pos: new THREE.Vector3(), + size: new THREE.Vector3(), + yaw: 0 + }; + ret.pos.x = +content['x_3d']; + ret.pos.y = +content['y_3d']; + ret.pos.z = +content['z_3d']; + ret.size.x = +content['width_3d']; + ret.size.y = +content['height_3d']; + ret.size.z = +content['length_3d']; + ret.yaw = +content['rotation_y']; + return ret; + } + fromContent(content) { + this.box.pos.x = +content['x_3d']; + this.box.pos.y = +content['y_3d']; + this.box.pos.z = +content['z_3d']; + this.box.size.x = +content['width_3d']; + this.box.size.y = +content['height_3d']; + this.box.size.z = +content['length_3d']; + this.box.yaw = +content['rotation_y']; + } + initCube() { + const mesh = new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material); + + const meshFrame = new BoxFrameObject(); + meshFrame.addTo(this.pcdTool._scene); + + const group = new THREE.Group(); + const corners = [ + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + ]; + corners.forEach(m => group.add(m)); + const edges = [ + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + ]; + edges.forEach(m => group.add(m)); + const zFace = [ + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + new THREE.Mesh( + BBoxParams.geometry, BBoxParams.material), + ]; + zFace.forEach(m => group.add(m)); + group.visible = false; + this.pcdTool._scene.add(group); + + this.cube = { + mesh: mesh, + meshFrame: meshFrame, + corners: corners, + edges: edges, + zFace: zFace, + editGroup: group + }; + this.updateCube(false); + } + updateCube(changed) { + const box = this.box; + const mesh = this.cube.mesh; + // TODO: check change flag + // TODO: clamp() all + mesh.position.set(box.pos.x, box.pos.y, box.pos.z); + mesh.scale.set(box.size.x, box.size.y, box.size.z); + mesh.rotation.z = box.yaw; + mesh.updateMatrixWorld(); + const meshFrame = this.cube.meshFrame; + meshFrame.setParam(box.pos, box.size, box.yaw); + const group = this.cube.editGroup; + group.position.set(box.pos.x, box.pos.y, box.pos.z); + group.rotation.z = box.yaw; + const w = EDIT_OBJ_SIZE; + const corners = this.cube.corners; + corners[0].position.set(box.size.x/2+w/2, 0, 0); + corners[0].scale.set(w, box.size.y, box.size.z+w); + corners[1].position.set(0, box.size.y/2+w/2, 0); + corners[1].scale.set(box.size.x, w, box.size.z+w); + corners[2].position.set(-box.size.x/2-w/2, 0, 0); + corners[2].scale.set(w, box.size.y, box.size.z+w); + corners[3].position.set(0, -box.size.y/2-w/2, 0); + corners[3].scale.set(box.size.x, w, box.size.z+w); + const edges = this.cube.edges; + edges[0].position.set(box.size.x/2+w/2, box.size.y/2+w/2, 0); + edges[0].scale.set(w, w, box.size.z+w); + edges[1].position.set(-box.size.x/2-w/2, box.size.y/2+w/2, 0); + edges[1].scale.set(w, w, box.size.z+w); + edges[2].position.set(box.size.x/2+w/2, -box.size.y/2-w/2, 0); + edges[2].scale.set(w, w, box.size.z+w); + edges[3].position.set(-box.size.x/2-w/2, -box.size.y/2-w/2, 0); + edges[3].scale.set(w, w, box.size.z+w); + const zFace = this.cube.zFace; + zFace[0].position.set(0, 0, box.size.z/2+w/2); + zFace[0].scale.set(box.size.x, box.size.y, w); + zFace[1].position.set(0, 0, -box.size.z/2-w/2); + zFace[1].scale.set(box.size.x, box.size.y, w); + if ( changed ) { + this.label.isChanged = true; + } + if (this.selected) { + this.pcdTool.setArrow(this); + } + } + rotateFront(n) { + const box = this.box; + let cnt = n % 4; + if (cnt == 0) { return; } + + if (cnt < 0) { + cnt = 4 + cnt; + } + if (cnt & 1) { + box.size.set(box.size.y, box.size.x, box.size.z); + } + box.yaw = (box.yaw + Math.PI / 2 * n) % (Math.PI * 2) + + this.updateParam(true); + } +} + + diff --git a/front/app/labeling_tool/reducers/annotation_reducer.js b/front/app/labeling_tool/reducers/annotation_reducer.js new file mode 100644 index 00000000..4e3365f9 --- /dev/null +++ b/front/app/labeling_tool/reducers/annotation_reducer.js @@ -0,0 +1,22 @@ + +import { + SET_TARGET_LABEL +} from '../actions/annotation_action'; + +const initialState = { + targetLabel: null +}; + +export default function annotationReducer(state = initialState, action = {}) { + let newState = {}; + switch (action.type) { + case SET_TARGET_LABEL: + newState = { + targetLabel: action.label + }; + break; + default: + return state; + } + return Object.assign({}, state, newState); +} diff --git a/front/app/labeling_tool/reducers/reducer.js b/front/app/labeling_tool/reducers/reducer.js new file mode 100644 index 00000000..237ff9a5 --- /dev/null +++ b/front/app/labeling_tool/reducers/reducer.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import annotation from './annotation_reducer'; +import tool from './tool_reducer'; + +export default combineReducers({ + annotation, + tool, +}); diff --git a/front/app/labeling_tool/reducers/tool_reducer.js b/front/app/labeling_tool/reducers/tool_reducer.js new file mode 100644 index 00000000..cc46a65f --- /dev/null +++ b/front/app/labeling_tool/reducers/tool_reducer.js @@ -0,0 +1,67 @@ + +import { + SET_TOOL_ANNOTATION, + SET_TOOL_KLASS_SET, + SET_TOOL_HISTORY, + SET_TOOL_CLIPBOARD, + SET_TOOL_CONTROLS, + SET_TOOL_LABEL_TOOL, + ADD_TOOL_WITH_INDEX, +} from '../actions/tool_action'; + +const initialState = { + annotation: null, + klassSet: null, + history: null, + clipboard: null, + labelTool: null, + tools: [], + toolsCnt: 0 +}; + +export default function toolReducer(state = initialState, action = {}) { + let newState = {}; + switch (action.type) { + case SET_TOOL_ANNOTATION: + newState = { + annotation: action.annotation + }; + break; + case SET_TOOL_KLASS_SET: + newState = { + klassSet: action.klassSet + }; + break; + case SET_TOOL_HISTORY: + newState = { + history: action.history + }; + break; + case SET_TOOL_CLIPBOARD: + newState = { + clipboard: action.clipboard + }; + break; + case SET_TOOL_CONTROLS: + newState = { + controls: action.controls + }; + break; + case SET_TOOL_LABEL_TOOL: + newState = { + labelTool: action.labelTool + }; + break; + case ADD_TOOL_WITH_INDEX: + const newTools = state.tools.slice(); + newTools[action.idx] = action.tool; + newState = { + tools: newTools, + toolsCnt: state.toolsCnt + 1 + } + break; + default: + return state; + } + return Object.assign({}, state, newState); +}