diff --git a/package-lock.json b/package-lock.json index 790a07849..7379a90b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1806,7 +1806,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, "requires": { "array-uniq": "^1.0.1" } @@ -1814,8 +1813,7 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { "version": "0.2.1", @@ -1832,8 +1830,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, "asap": { "version": "2.0.6", @@ -1909,7 +1906,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "dev": true, "requires": { "lodash": "^4.17.10" } @@ -2878,8 +2874,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -3008,12 +3003,6 @@ "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", "dev": true }, - "binaryextensions": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.2.tgz", - "integrity": "sha512-xVNN69YGDghOqCCtA6FI7avYrr02mTJjOgB0/f1VPD3pJC8QEvjTKWc4epDx8AqxxA75NI0QpVM2gPJXUbE4Tg==", - "dev": true - }, "bitsyntax": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", @@ -3235,7 +3224,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3560,8 +3548,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-indexof": { "version": "1.1.1", @@ -3609,8 +3596,7 @@ "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, "builtin-status-codes": { "version": "3.0.0", @@ -3996,7 +3982,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "dev": true, "requires": { "colors": "1.0.3" }, @@ -4004,8 +3989,7 @@ "colors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" } } }, @@ -4030,8 +4014,7 @@ "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, "clipboard": { "version": "2.0.4", @@ -4087,14 +4070,7 @@ "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "clone-response": { "version": "1.0.2", @@ -4108,19 +4084,7 @@ "clone-stats": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", - "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" }, "co": { "version": "4.6.0", @@ -4557,14 +4521,12 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -4814,8 +4776,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -5245,7 +5206,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -5547,12 +5507,6 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, - "detect-conflict": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.1.tgz", - "integrity": "sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=", - "dev": true - }, "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -6025,28 +5979,12 @@ "safer-buffer": "^2.1.0" } }, - "editions": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz", - "integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==", - "dev": true, - "requires": { - "errlop": "^1.1.1", - "semver": "^5.6.0" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, - "ejs": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", - "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", - "dev": true - }, "electron-to-chromium": { "version": "1.3.98", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.98.tgz", @@ -6210,15 +6148,6 @@ "java-properties": "^1.0.0" } }, - "errlop": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/errlop/-/errlop-1.1.1.tgz", - "integrity": "sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw==", - "dev": true, - "requires": { - "editions": "^2.1.2" - } - }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -6228,21 +6157,10 @@ "prr": "~1.0.1" } }, - "error": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", - "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", - "dev": true, - "requires": { - "string-template": "~0.2.1", - "xtend": "~4.0.0" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -6328,8 +6246,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.11.0", @@ -6870,8 +6787,7 @@ "exit-hook": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" }, "expand-braces": { "version": "0.1.2", @@ -7167,8 +7083,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -7734,7 +7649,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, "requires": { "locate-path": "^2.0.0" } @@ -7925,6 +7839,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, + "optional": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -8060,6 +7975,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, + "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -8235,6 +8151,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, + "optional": true, "requires": { "graceful-fs": "^4.1.6" } @@ -8326,7 +8243,8 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", @@ -8492,6 +8410,7 @@ "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8913,64 +8832,6 @@ "assert-plus": "^1.0.0" } }, - "gh-got": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-6.0.0.tgz", - "integrity": "sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==", - "dev": true, - "requires": { - "got": "^7.0.0", - "is-plain-obj": "^1.1.0" - }, - "dependencies": { - "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "dev": true, - "requires": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" - } - }, - "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", - "dev": true - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "^1.0.1" - } - } - } - }, "ghooks": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ghooks/-/ghooks-2.0.2.tgz", @@ -9043,7 +8904,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9215,8 +9075,7 @@ "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "graceful-readlink": { "version": "1.0.1", @@ -9228,7 +9087,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.3.tgz", "integrity": "sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=", - "dev": true, "requires": { "lodash": "^4.17.2" } @@ -9314,7 +9172,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9530,8 +9387,7 @@ "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, "hpack.js": { "version": "2.1.6", @@ -10695,8 +10551,7 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, "is-property": { "version": "1.0.2", @@ -10778,8 +10633,7 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-windows": { "version": "1.0.2", @@ -10795,8 +10649,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { "version": "3.0.3", @@ -17132,8 +16985,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "2.1.0", @@ -17176,14 +17028,12 @@ "os-shim": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=" }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "output-file-sync": { "version": "1.1.2", @@ -17254,7 +17104,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, "requires": { "p-try": "^1.0.0" } @@ -17263,7 +17112,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -17302,8 +17150,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, "pac-proxy-agent": { "version": "3.0.0", @@ -17492,7 +17339,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, "requires": { "error-ex": "^1.2.0" } @@ -17553,8 +17399,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -17570,8 +17415,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-platform": { "version": "0.11.15", @@ -17608,7 +17452,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, "requires": { "pify": "^2.0.0" }, @@ -17616,8 +17459,7 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" } } }, @@ -17670,14 +17512,12 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "^2.0.0" } @@ -17785,12 +17625,6 @@ "integrity": "sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==", "dev": true }, - "pretty-bytes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", - "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=", - "dev": true - }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", @@ -17825,8 +17659,7 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "2.0.3", @@ -17939,8 +17772,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.31", @@ -18152,7 +17984,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, "requires": { "load-json-file": "^2.0.0", "normalize-package-data": "^2.3.2", @@ -18163,7 +17994,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, "requires": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" @@ -18173,7 +18003,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -18501,7 +18330,6 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, "requires": { "resolve": "^1.1.6" } @@ -18704,8 +18532,7 @@ "replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" }, "request": { "version": "2.88.0", @@ -18778,7 +18605,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -18885,7 +18711,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, "requires": { "glob": "^7.0.5" } @@ -18924,7 +18749,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, "requires": { "is-promise": "^2.1.0" } @@ -18952,8 +18776,7 @@ "rx": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", - "dev": true + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" }, "rx-lite": { "version": "4.0.8", @@ -18982,8 +18805,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -19504,7 +19326,6 @@ "version": "0.7.6", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=", - "dev": true, "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -19941,7 +19762,6 @@ "version": "1.0.15", "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, "requires": { "concat-stream": "^1.4.7", "os-shim": "^0.1.2" @@ -19951,7 +19771,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -19960,14 +19779,12 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -19976,8 +19793,7 @@ "spdx-license-ids": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", - "dev": true + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==" }, "spdy": { "version": "4.0.1", @@ -20249,12 +20065,6 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", - "dev": true - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -20280,7 +20090,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -20303,14 +20112,12 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" }, "strip-bom-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "dev": true, "requires": { "first-chunk-stream": "^2.0.0", "strip-bom": "^2.0.0" @@ -20320,7 +20127,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, "requires": { "is-utf8": "^0.2.0" } @@ -20578,14 +20384,7 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "textextensions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.4.0.tgz", - "integrity": "sha512-qftQXnX1DzpSV8EddtHIT0eDDEiBF8ywhFYR2lI9xrGtxqKN+CvLXhACeCIGbCpQfxxERbrkZEFb8cZcDKbVZA==", - "dev": true + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "tfjs-image-recognition-base": { "version": "0.6.1", @@ -20612,7 +20411,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -20864,8 +20662,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typeface-oswald": { "version": "0.0.54", @@ -21257,8 +21054,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -21865,7 +21661,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -21876,7 +21671,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.3.0", @@ -21889,14 +21683,12 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, "requires": { "is-utf8": "^0.2.0" } @@ -22678,8 +22470,7 @@ "v8-compile-cache": "^1.1.2", "webpack-addons": "^1.1.5", "yargs": "9.0.1", - "yeoman-environment": "^2.0.0", - "yeoman-generator": "github:ev1stensberg/generator#9e24fa31c85302ca1145ae34fc68b4f133251ca0" + "yeoman-environment": "^2.0.0" }, "dependencies": { "ansi-escapes": { @@ -22784,6 +22575,11 @@ "which": "^1.2.9" } }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" + }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -22813,6 +22609,18 @@ "escape-string-regexp": "^1.0.5" } }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -22837,6 +22645,19 @@ "which": "^1.2.14" } }, + "globby": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz", + "integrity": "sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg=", + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^6.0.1", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, "inquirer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", @@ -22864,6 +22685,11 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -22879,6 +22705,11 @@ "mimic-fn": "^1.0.0" } }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -22936,6 +22767,14 @@ "os-tmpdir": "~1.0.2" } }, + "untildify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", + "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", + "requires": { + "os-homedir": "^1.0.0" + } + }, "yargs": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", @@ -22965,6 +22804,214 @@ "requires": { "camelcase": "^4.1.0" } + }, + "yeoman-generator": { + "version": "github:ev1stensberg/generator#9e24fa31c85302ca1145ae34fc68b4f133251ca0", + "from": "github:ev1stensberg/generator#9e24fa31c85302ca1145ae34fc68b4f133251ca0", + "requires": { + "async": "^2.0.0", + "chalk": "^1.0.0", + "cli-table": "^0.3.1", + "cross-spawn": "^5.0.1", + "dargs": "^5.1.0", + "dateformat": "^2.0.0", + "debug": "^2.1.0", + "detect-conflict": "^1.0.0", + "error": "^7.0.2", + "find-up": "^2.1.0", + "github-username": "^4.0.0", + "istextorbinary": "^2.1.0", + "lodash": "^4.11.1", + "mem-fs-editor": "^3.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.0", + "pretty-bytes": "^4.0.2", + "read-chunk": "^2.0.0", + "read-pkg-up": "^2.0.0", + "rimraf": "^2.2.0", + "run-async": "^2.0.0", + "shelljs": "^0.7.0", + "text-table": "^0.2.0", + "through2": "^2.0.0", + "yeoman-environment": "^1.1.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "diff": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", + "integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=" + }, + "external-editor": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", + "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", + "requires": { + "extend": "^3.0.0", + "spawn-sync": "^1.0.15", + "tmp": "^0.0.29" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "inquirer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", + "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", + "requires": { + "ansi-escapes": "^1.1.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "external-editor": "^1.1.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "mute-stream": "0.0.6", + "pinkie-promise": "^2.0.0", + "run-async": "^2.2.0", + "rx": "^4.1.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "requires": { + "chalk": "^1.0.0" + } + }, + "mute-stream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", + "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=" + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "yeoman-environment": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-1.6.6.tgz", + "integrity": "sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE=", + "requires": { + "chalk": "^1.0.0", + "debug": "^2.0.0", + "diff": "^2.1.2", + "escape-string-regexp": "^1.0.2", + "globby": "^4.0.0", + "grouped-queue": "^0.3.0", + "inquirer": "^1.0.2", + "lodash": "^4.11.1", + "log-symbols": "^1.0.1", + "mem-fs": "^1.1.0", + "text-table": "^0.2.0", + "untildify": "^2.0.0" + } + } + } } } }, @@ -24501,8 +24548,7 @@ "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { "version": "3.2.1", @@ -24512,8 +24558,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargonaut": { "version": "1.1.4", @@ -24929,129 +24974,6 @@ } } } - }, - "yeoman-generator": { - "version": "github:ev1stensberg/generator#9e24fa31c85302ca1145ae34fc68b4f133251ca0", - "from": "github:ev1stensberg/generator#Feature-getArgument", - "dev": true, - "requires": { - "async": "^2.0.0", - "chalk": "^1.0.0", - "cli-table": "^0.3.1", - "cross-spawn": "^5.0.1", - "dargs": "^5.1.0", - "dateformat": "^2.0.0", - "debug": "^2.1.0", - "detect-conflict": "^1.0.0", - "error": "^7.0.2", - "find-up": "^2.1.0", - "github-username": "^4.0.0", - "istextorbinary": "^2.1.0", - "lodash": "^4.11.1", - "mem-fs-editor": "^3.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.0", - "pretty-bytes": "^4.0.2", - "read-chunk": "^2.0.0", - "read-pkg-up": "^2.0.0", - "rimraf": "^2.2.0", - "run-async": "^2.0.0", - "shelljs": "^0.7.0", - "text-table": "^0.2.0", - "through2": "^2.0.0", - "yeoman-environment": "^1.1.0" - }, - "dependencies": { - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, - "diff": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", - "integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=", - "dev": true - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globby": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz", - "integrity": "sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^6.0.1", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "untildify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", - "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "yeoman-environment": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-1.6.6.tgz", - "integrity": "sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE=", - "dev": true, - "requires": { - "chalk": "^1.0.0", - "debug": "^2.0.0", - "diff": "^2.1.2", - "escape-string-regexp": "^1.0.2", - "globby": "^4.0.0", - "grouped-queue": "^0.3.0", - "inquirer": "^1.0.2", - "lodash": "^4.11.1", - "log-symbols": "^1.0.1", - "mem-fs": "^1.1.0", - "text-table": "^0.2.0", - "untildify": "^2.0.0" - } - } - } } } } diff --git a/package.json b/package.json index d2588a096..c79267dad 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "start": "webpack-dev-server --open --config webpack.dev.babel.js", "manual-test": "webpack-dev-server --open --config webpack.test.babel.js", "build": "webpack --config webpack.prod.babel.js", - "test": "./node_modules/karma/bin/karma start karma.conf.js", + "test": "karma start karma.conf.js", "test:single": "./node_modules/karma/bin/karma start karma.conf.js --single-run", "test-travis": "./scripts/test-travis.sh", "serve:docs": "docsify serve docs", diff --git a/src/ObjectDetector/YOLO/index.js b/src/ObjectDetector/YOLO/index.js index ec479ccc4..4762c9920 100644 --- a/src/ObjectDetector/YOLO/index.js +++ b/src/ObjectDetector/YOLO/index.js @@ -1,3 +1,4 @@ +/* eslint-disable class-methods-use-this */ // Copyright (c) 2018 ml5 // // This software is released under the MIT License. @@ -7,257 +8,353 @@ /* YOLO Object detection Heavily derived from https://github.com/ModelDepot/tfjs-yolo-tiny (ModelDepot: modeldepot.io) +AND https://github.com/TheHidden1/yolo.js */ import * as tf from '@tensorflow/tfjs'; import Video from './../../utils/Video'; -import { - imgToTensor, - isInstanceOfSupportedElement -} from "./../../utils/imageUtilities"; +import { imgToTensorYOLO, isInstanceOfSupportedElement } from './../../utils/imageUtilities'; + +import isTensorArray from './../../utils/tensorUtilities'; import callCallback from './../../utils/callcallback'; import CLASS_NAMES from './../../utils/COCO_CLASSES'; import modelLoader from './../../utils/modelLoader'; -import { - nonMaxSuppression, - boxesToCorners, - head, - filterBoxes, -} from './postprocess'; +// these are the default you yolo v2 const DEFAULTS = { - modelUrl: 'https://raw.githubusercontent.com/ml5js/ml5-data-and-training/master/models/YOLO/model.json', - filterBoxesThreshold: 0.01, - IOUThreshold: 0.4, - classProbThreshold: 0.4, + modelName: 'tiny-yolo-v2', + modelUrl: 'https://raw.githubusercontent.com/ml5js/ml5-data-and-training/master/models/YOLO/model.json', + filterBoxesThreshold: 0.01, + IOUThreshold: 0.4, + classProbThreshold: 0.4, + modelSize: [224, 224], + maxOutput: 10, + masks: [[0, 1, 2, 3, 4]], + anchors: [ + [0.57273, 0.677385], + [1.87446, 2.06253], + [3.33843, 5.47434], + [7.88282, 3.52778], + [9.77052, 9.16828], + ], }; -// Size of the video -const imageSize = 416; - class YOLOBase extends Video { - /** - * @deprecated Please use ObjectDetector class instead - */ - /** - * @typedef {Object} options - * @property {number} filterBoxesThreshold - default 0.01 - * @property {number} IOUThreshold - default 0.4 - * @property {number} classProbThreshold - default 0.4 - */ - /** - * Create YOLO model. Works on video and images. - * @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} video - Optional. The video to be used for object detection and classification. - * @param {Object} options - Optional. A set of options. - * @param {function} callback - Optional. A callback function that is called once the model has loaded. - */ - constructor(video, options, callback) { - super(video, imageSize); - - this.modelUrl = options.modelUrl || DEFAULTS.modelUrl; - this.filterBoxesThreshold = options.filterBoxesThreshold || DEFAULTS.filterBoxesThreshold; - this.IOUThreshold = options.IOUThreshold || DEFAULTS.IOUThreshold; - this.classProbThreshold = options.classProbThreshold || DEFAULTS.classProbThreshold; - this.modelReady = false; - this.isPredicting = false; - this.callback = callback; - this.ready = callCallback(this.loadModel(), this.callback); - - if (!options.disableDeprecationNotice) { - console.warn("WARNING! Function YOLO has been deprecated, please use the new ObjectDetector function instead"); - } - } - - async loadModel() { - if (this.videoElt && !this.video) { - this.video = await this.loadVideo(); - } - - if (modelLoader.isAbsoluteURL(this.modelUrl) === true) { - this.model = await tf.loadLayersModel(this.modelUrl); - } else { - const modelPath = modelLoader.getModelPath(this.modelUrl); - this.modelUrl = `${modelPath}/model.json`; - this.model = await tf.loadLayersModel(this.modelUrl); - } - - this.modelReady = true; - return this; - } - - /** - * Detect objects that are in video, returns bounding box, label, and confidence scores - * @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} subject - Subject of the detection. - * @param {function} callback - Optional. A callback function that is called once the model has loaded. If no callback is provided, it will return a promise - * that will be resolved once the prediction is done. - * @returns {ObjectDetectorPrediction} - */ - async detect(inputOrCallback, cb) { - await this.ready; - let imgToPredict; - let callback = cb; - - if (isInstanceOfSupportedElement(inputOrCallback)) { - imgToPredict = inputOrCallback; - } else if (typeof inputOrCallback === "object" && isInstanceOfSupportedElement(inputOrCallback.elt)) { - imgToPredict = inputOrCallback.elt; // Handle p5.js image and video. - } else if (typeof inputOrCallback === "object" && isInstanceOfSupportedElement(inputOrCallback.canvas)) { - imgToPredict = inputOrCallback.canvas; // Handle p5.js image and video. - } else if (typeof inputOrCallback === "function") { - imgToPredict = this.video; - callback = inputOrCallback; - } else { - throw new Error('Detection subject not supported') - } - - return callCallback(this.detectInternal(imgToPredict), callback); - } - - /** - * @typedef {Object} ObjectDetectorPrediction - * @property {number} x - top left x coordinate of the prediction box in pixels. - * @property {number} y - top left y coordinate of the prediction box in pixels. - * @property {number} width - width of the prediction box in pixels. - * @property {number} height - height of the prediction box in pixels. - * @property {string} label - the label given. - * @property {number} confidence - the confidence score (0 to 1). - * @property {ObjectDetectorPredictionNormalized} normalized - a normalized object of the predicition - */ - - /** - * @typedef {Object} ObjectDetectorPredictionNormalized - * @property {number} x - top left x coordinate of the prediction box (0 to 1). - * @property {number} y - top left y coordinate of the prediction box (0 to 1). - * @property {number} width - width of the prediction box (0 to 1). - * @property {number} height - height of the prediction box (0 to 1). - */ - /** - * Detect objects that are in video, returns bounding box, label, and confidence scores - * @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} subject - Subject of the detection. - * @returns {ObjectDetectorPrediction} - */ - async detectInternal(imgToPredict) { - await this.ready; - await tf.nextFrame(); - - const ANCHORS = tf.tensor2d([ - [0.57273, 0.677385], - [1.87446, 2.06253], - [3.33843, 5.47434], - [7.88282, 3.52778], - [9.77052, 9.16828], - ]); - - this.isPredicting = true; - const [allBoxes, boxConfidence, boxClassProbs] = tf.tidy(() => { - const input = imgToTensor(imgToPredict, [imageSize, imageSize]); - const activation = this.model.predict(input); - const [boxXY, boxWH, bConfidence, bClassProbs] = head(activation, ANCHORS, 80); - const aBoxes = boxesToCorners(boxXY, boxWH); - return [aBoxes, bConfidence, bClassProbs]; - }); - - const [boxes, scores, classes] = await filterBoxes(allBoxes, boxConfidence, boxClassProbs, this.filterBoxesThreshold); - - allBoxes.dispose(); - boxConfidence.dispose(); - boxClassProbs.dispose(); - // If all boxes have been filtered out - if (boxes == null) { - return []; - } - return tf.tidy(() => { - const width = tf.scalar(imageSize); - const height = tf.scalar(imageSize); - const imageDims = tf.stack([height, width, height, width]).reshape([1, 4]); - const boxesModified = tf.mul(boxes, imageDims); - - const preKeepBoxesArr = boxesModified.dataSync(); - const scoresArr = scores.dataSync(); - - const [keepIndx, boxesArr, keepScores] = nonMaxSuppression( - preKeepBoxesArr, - scoresArr, - this.IOUThreshold, - ); - - const classesIndxArr = classes.gather(tf.tensor1d(keepIndx, 'int32')).dataSync(); - - const results = []; - - classesIndxArr.forEach((classIndx, i) => { - const classProb = keepScores[i]; - if (classProb < this.classProbThreshold) { - return; - } - - const className = CLASS_NAMES[classIndx]; - let [y, x, h, w] = boxesArr[i]; - - y = Math.max(0, y); - x = Math.max(0, x); - h = Math.min(imageSize, h) - y; - w = Math.min(imageSize, w) - x; - - const resultObj = { - label: className, - confidence: classProb, - x, - y, - width: w, - height: h, - normalized: { - x: x / imageSize, - y: y / imageSize, - width: w / imageSize, - height: h / imageSize, - } - }; - - results.push(resultObj); - }); - - this.isPredicting = false; - - width.dispose() - height.dispose() - imageDims.dispose() - boxesModified.dispose() - boxes.dispose(); - scores.dispose(); - classes.dispose(); - ANCHORS.dispose(); - - - return results; - }) - - } + + /** + * @deprecated Please use ObjectDetector class instead + */ + /** + * @typedef {Object} options + * @property {number} filterBoxesThreshold - default 0.01 + * @property {number} IOUThreshold - default 0.4 + * @property {number} classProbThreshold - default 0.4 + */ + /** + * Create YOLO model. Works on video and images. + * @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} video - Optional. The video to be used for object detection and classification. + * @param {Object} options - Optional. A set of options. + * @param {function} callback - Optional. A callback function that is called once the model has loaded. + */ + constructor(video, options, callback) { + const imageSize = options.modelSize[0] || DEFAULTS.modelSize[0] + super(video, imageSize); + + this.modelUrl = options.modelUrl || DEFAULTS.modelUrl; + this.modelName = options.modelName || DEFAULTS.modelName; + + + this.modelSize = options.modelSize || DEFAULTS.modelSize; + this.filterBoxesThreshold = options.filterBoxesThreshold || DEFAULTS.filterBoxesThreshold; + this.IOUThreshold = options.IOUThreshold || DEFAULTS.IOUThreshold; + this.classProbThreshold = options.classProbThreshold || DEFAULTS.classProbThreshold; + this.modelSize = options.modelSize || DEFAULTS.modelSize; + this.maxOutput = options.maxOutput || DEFAULTS.maxOutput; + this.masks = options.masks || DEFAULTS.masks; + this.anchors = options.anchors || DEFAULTS.anchors; + + + + this.modelReady = false; + this.isPredicting = false; + this.callback = callback; + this.ready = callCallback(this.loadModel(), this.callback); + + if (!options.disableDeprecationNotice) { + console.warn( + 'WARNING! Function YOLO has been deprecated, please use the new ObjectDetector function instead', + ); + } + } + + async loadModel() { + if (this.videoElt && !this.video) { + this.video = await this.loadVideo(); + } + + if (modelLoader.isAbsoluteURL(this.modelUrl) === true) { + this.model = await tf.loadLayersModel(this.modelUrl); + } else { + const modelPath = modelLoader.getModelPath(this.modelUrl); + this.modelUrl = `${modelPath}/model.json`; + this.model = await tf.loadLayersModel(this.modelUrl); + } + + this.modelReady = true; + return this; + } + + /** + * Detect objects that are in video, returns bounding box, label, and confidence scores + * @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} subject - Subject of the detection. + * @param {function} callback - Optional. A callback function that is called once the model has loaded. If no callback is provided, it will return a promise + * that will be resolved once the prediction is done. + * @returns {ObjectDetectorPrediction} + */ + async detect(inputOrCallback, cb) { + await this.ready; + let imgToPredict; + let callback = cb; + + if (isInstanceOfSupportedElement(inputOrCallback)) { + imgToPredict = inputOrCallback; + } else if ( + typeof inputOrCallback === 'object' && + isInstanceOfSupportedElement(inputOrCallback.elt) + ) { + imgToPredict = inputOrCallback.elt; // Handle p5.js image and video. + } else if ( + typeof inputOrCallback === 'object' && + isInstanceOfSupportedElement(inputOrCallback.canvas) + ) { + imgToPredict = inputOrCallback.canvas; // Handle p5.js image and video. + } else if (typeof inputOrCallback === 'function') { + imgToPredict = this.video; + callback = inputOrCallback; + } else { + throw new Error('Detection subject not supported'); + } + + return callCallback(this.detectInternal(imgToPredict), callback); + } + + /** + * Detect objects that are in video, returns bounding box, label, and confidence scores + * @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} subject - Subject of the detection. + * @returns {ObjectDetectorPrediction} + */ + async detectInternal(imgToPredict) { + await this.ready; + await tf.nextFrame(); + + this.isPredicting = true; + let originalImageSize = null; + const [rawBoxes, rawScores] = tf.tidy(() => { + const [ originalImageShape , input ] = imgToTensorYOLO(imgToPredict, this.modelSize); + originalImageSize = originalImageShape; + const activation = this.model.predict(input); + const [rawboxes, rawscores] = this.postProcessRawPredictions(activation); + + // removes batch dim + const boxes = rawboxes.squeeze([0]); + const scores = rawscores.squeeze([0]); + return [boxes, scores]; + }); + const [boxes, scores, classes] = await this.postProcessBoxes(rawBoxes, rawScores); + + const dectections = this.createDetectionArray(boxes,scores,classes, this.modelSize, originalImageSize); + this.isPredicting = false; + return dectections; + } + + /** + * the postprocessing function for the yolo object detection algorithm + * @param rawPrediction can be a `tf.Tensor` representing a single output (yolov2) + * or a `tf.Tensor[]` representing multiple outputs (yolov3 has 3 outputs at 3 diffrent scales). + * each output has the shape of `[batch, size, size, ( numClasses + 5 ) * numAnchors]` + * with the 5 representing: Box Coodinates [4] + BoxConfidence [1] + * + * @return a `tf.Tensor[]` that contain `[Boxes, Scores]` + * `Boxes` with a shape of `[batch, numBoxes, 4]` + * Each `box` is defined by `[topY, topX, bottomY, bottomX]` + * + * `Scores` with a shape of `[batch, numBoxes, numClasses]` + */ + postProcessRawPredictions(rawPrediction) { + const layers = []; + let isV3 = false; + + if (isTensorArray(rawPrediction)) { + for (let i = 0; i < rawPrediction.length; i += 1) { + layers.push(rawPrediction[i]); + } + isV3 = true; + } else { + layers.push(rawPrediction); + } + + const boxes = []; + const probs = []; + + for (let i = 0; i < layers.length; i += 1) { + const mask = this.masks[i]; + const anchors = tf.gather(this.anchors, mask).expandDims(0); + const [box, prob] = this.processLayer(layers[i], anchors, this.modelSize, isV3); + boxes.push(box); + probs.push(prob); + } + + const boxesTensor = tf.concat(boxes, 1); + const probsTensor = tf.concat(probs, 1); + + return [boxesTensor, probsTensor]; + } + + /** + * Process 1 layer of the yolo output + * @param prediction a `tf.Tensor` representing 1 output of the neural net + * @param anchorsTensor a `tf.Tensor` representing the anchors that correspond with the output + * shape: `[numAnchors, 2]` + * @param modelSize the input size for the neural net + * @param isV3 boolean indecating if its a v2 or a v3 model + * + * @return a `tf.Tensor[]` that containes `[boxes , Scores]` that correspond to the specific layer + */ + processLayer(prediction, anchors, modelSize, isV3) { + + const numAnchors = anchors.shape[1]; + const [batch, outputHeight, outputWidth, data] = prediction.shape; + const numBoxes = outputWidth * outputHeight * numAnchors; + const dataTensorLen = data / numAnchors; + const numClasses = dataTensorLen - 5; + + const reshaped = tf.reshape(prediction, [batch, outputHeight, outputWidth, numAnchors, dataTensorLen]); + + const scalefactor = [modelSize[0] / outputHeight, modelSize[1] / outputWidth]; + + let [boxXY, boxWH, boxConfidence, boxClassProbs] = tf.split(reshaped, [2, 2, 1, numClasses], 4); + + const gridX = tf.tile(tf.reshape(tf.range(0, outputWidth), [1, -1, 1, 1]), [outputHeight, 1, 1, 1]); + const gridY = tf.tile(tf.reshape(tf.range(0, outputHeight), [-1, 1, 1, 1]), [1, outputWidth, 1, 1]); + + let indexGrid = tf.concat([gridX, gridY], 3); + indexGrid = tf.tile(indexGrid, [1, 1, numAnchors, 1]); + + let anchorsTensor = anchors; + if (isV3) { + anchorsTensor = anchors.div(scalefactor); + } + + boxXY = tf.div(tf.add(tf.sigmoid(boxXY), indexGrid), [outputWidth, outputHeight]); + boxWH = tf.div(tf.mul(tf.exp(boxWH), anchorsTensor), [outputWidth, outputHeight]); + + const boxYX = tf.concat(tf.split(boxXY, 2, 4).reverse(), 4); + const boxHW = tf.concat(tf.split(boxWH, 2, 4).reverse(), 4); + + // XY WH to XmaxYmax XminYmin + const boxMins = tf.mul(tf.sub(boxYX, tf.div(boxHW, 2)), modelSize); + const boxMaxes = tf.mul(tf.add(boxYX, tf.div(boxHW, 2)), modelSize); + + const boxes = tf.concat([...tf.split(boxMins, 2, 4), ...tf.split(boxMaxes, 2, 4)], 4).reshape([batch, numBoxes, 4]); + + boxConfidence = tf.sigmoid(boxConfidence); + boxClassProbs = tf.softmax(boxClassProbs); + + const classProbs = tf.mul(boxConfidence, boxClassProbs).reshape([batch, numBoxes, numClasses]); + + return [boxes, classProbs]; + } + + async postProcessBoxes(rawBoxes, rawScores) { + const scores = tf.max(rawScores, -1); + const classes = tf.argMax(rawScores, -1); + + const indiceTensor = await tf.image.nonMaxSuppressionAsync(rawBoxes, scores, this.maxOutput, this.iouThreshold, this.classProbThreshold); + const filteredBoxes = tf.gather(rawBoxes, indiceTensor).array(); // [batch, n, 4] + const filteredScores = tf.gather(scores, indiceTensor).array(); // [batch, n, ] + const filteredClasses = tf.gather(classes, indiceTensor).array(); // [batch, n, ] + return Promise.all([filteredBoxes, filteredScores, filteredClasses]); + } + + /** + * The final phase in the post processing that outputs the final `Detection[]` + * @param finalBoxes an array containing the raw box information + * @param imageDims a `Shape` containing the original image dimensions `[height, width]` + * @param inputDims a `Shape` containing the model input dimensions `[height, width]` + * @return a `DetectorOutput` with the final collected boxes + */ + createDetectionArray(boxes, scores, classes, modelSize, originalInputSize) { + const numDetections = classes.length; // || scores.length; + const detections = []; + for (let i = 0; i < numDetections; i += 1) { + // debugger; + const topY = boxes[i][0]; + const topX = boxes[i][1]; + const bottomY = boxes[i][2]; + const bottomX = boxes[i][3]; + + const w = bottomX - topX; + const h = bottomY - topY; + + const scaleX = originalInputSize[1] / modelSize[1]; // width + const scaleY = originalInputSize[0] / modelSize[0]; // height + + const classIndex = classes[i]; + const label = CLASS_NAMES[classIndex]; + + const score = scores[i]; + + const detection = { + raw: { + x : topX, + y : topY, + w, + h, + imageWidth : modelSize[1], + imageHeight : modelSize[0], + }, + + labelIndex: classIndex, + label, + confidence : score, + + x: topX * scaleX, + y: topY * scaleY, + w: w * scaleX, + h: h * scaleY, + + imageWidth : originalInputSize[1], + imageHeight : originalInputSize[0], + }; + detections.push(detection); + } + return detections; + } } const YOLO = (videoOr, optionsOr, cb) => { - let video = null; - let options = {}; - let callback = cb; - - if (videoOr instanceof HTMLVideoElement) { - video = videoOr; - } else if (typeof videoOr === 'object' && videoOr.elt instanceof HTMLVideoElement) { - video = videoOr.elt; // Handle p5.js image - } else if (typeof videoOr === 'function') { - callback = videoOr; - } else if (typeof videoOr === 'object') { - options = videoOr; - } - - if (typeof optionsOr === 'object') { - options = optionsOr; - } else if (typeof optionsOr === 'function') { - callback = optionsOr; - } - - return new YOLOBase(video, options, callback); + let video = null; + let options = {}; + let callback = cb; + + if (videoOr instanceof HTMLVideoElement) { + video = videoOr; + } else if (typeof videoOr === 'object' && videoOr.elt instanceof HTMLVideoElement) { + video = videoOr.elt; // Handle p5.js image + } else if (typeof videoOr === 'function') { + callback = videoOr; + } else if (typeof videoOr === 'object') { + options = videoOr; + } + + if (typeof optionsOr === 'object') { + options = optionsOr; + } else if (typeof optionsOr === 'function') { + callback = optionsOr; + } + + return new YOLOBase(video, options, callback); }; -export default YOLO; \ No newline at end of file +export default YOLO; diff --git a/src/ObjectDetector/YOLO/index_test.js b/src/ObjectDetector/YOLO/index_test.js index 899ebea62..72b09c6a4 100644 --- a/src/ObjectDetector/YOLO/index_test.js +++ b/src/ObjectDetector/YOLO/index_test.js @@ -5,10 +5,11 @@ const YOLO_DEFAULTS = { - IOUThreshold: 0.4, - classProbThreshold: 0.4, filterBoxesThreshold: 0.01, - size: 416, + IOUThreshold: 0.4, + classProbThreshold: 0.4, + modelSize: [416, 416], + maxOutput: 10, }; describe('YOLO', () => { @@ -48,12 +49,13 @@ describe('YOLO', () => { expect(yolo.IOUThreshold).toBe(YOLO_DEFAULTS.IOUThreshold); expect(yolo.classProbThreshold).toBe(YOLO_DEFAULTS.classProbThreshold); expect(yolo.filterBoxesThreshold).toBe(YOLO_DEFAULTS.filterBoxesThreshold); - expect(yolo.size).toBe(YOLO_DEFAULTS.size); }); it('detects a robin', async () => { + const robin = await getRobin(); const detection = await yolo.detect(robin); + expect(detection[0].label).toBe('bird'); }); diff --git a/src/ObjectDetector/YOLO/postprocess.js b/src/ObjectDetector/YOLO/postprocess.js deleted file mode 100644 index 4b05c4e99..000000000 --- a/src/ObjectDetector/YOLO/postprocess.js +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) 2018 ml5 -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -// Heavily derived from YAD2K (https://github.com/allanzelener/YAD2K) -/* eslint max-len: ["error", { "code": 180 }] */ - -import * as tf from '@tensorflow/tfjs'; - -// export const ANCHORS = tf.tensor2d([ -// [0.57273, 0.677385], -// [1.87446, 2.06253], -// [3.33843, 5.47434], -// [7.88282, 3.52778], -// [9.77052, 9.16828], -// ]); - -export const boxIntersection = (a, b) => { - const w = Math.min(a[3], b[3]) - Math.max(a[1], b[1]); - const h = Math.min(a[2], b[2]) - Math.max(a[0], b[0]); - if (w < 0 || h < 0) { - return 0; - } - return w * h; -}; - -export const boxUnion = (a, b) => { - const i = boxIntersection(a, b); - return (((a[3] - a[1]) * (a[2] - a[0])) + ((b[3] - b[1]) * (b[2] - b[0]))) - i; -}; - -export const boxIOU = (a, b) => boxIntersection(a, b) / boxUnion(a, b); - -export async function filterBoxes( - boxes, - boxConfidence, - boxClassProbs, - threshold, -) { - - return tf.tidy(() => { - - const boxScores = tf.mul(boxConfidence, boxClassProbs); - const boxClasses = tf.argMax(boxScores, -1); - const boxClassScores = tf.max(boxScores, -1); - - const predictionMask = tf.greaterEqual(boxClassScores, tf.scalar(threshold)); - - const maskArr = predictionMask.dataSync(); - - const indicesArr = []; - for (let i = 0; i < maskArr.length; i += 1) { - const v = maskArr[i]; - if (v) { - indicesArr.push(i); - } - } - - if (indicesArr.length === 0) { - return [null, null, null]; - } - - const indices = tf.tensor1d(indicesArr, 'int32'); - - const result = [ - tf.gather(boxes.reshape([maskArr.length, 4]), indices), - tf.gather(boxClassScores.flatten(), indices), - tf.gather(boxClasses.flatten(), indices), - ]; - - // boxes.dispose(); - // boxClassScores.dispose(); - // boxClasses.dispose(); - - return result; - }) -} - -export const boxesToCorners = (boxXY, boxWH) => { - return tf.tidy(() => { - const two = tf.tensor1d([2.0]); - const boxMins = tf.sub(boxXY, tf.div(boxWH, two)); - const boxMaxes = tf.add(boxXY, tf.div(boxWH, two)); - - const dim0 = boxMins.shape[0]; - const dim1 = boxMins.shape[1]; - const dim2 = boxMins.shape[2]; - const size = [dim0, dim1, dim2, 1]; - - return tf.concat([ - boxMins.slice([0, 0, 0, 1], size), - boxMins.slice([0, 0, 0, 0], size), - boxMaxes.slice([0, 0, 0, 1], size), - boxMaxes.slice([0, 0, 0, 0], size), - ], 3); - }) -}; - -export const nonMaxSuppression = (boxes, scores, iouThreshold) => { - return tf.tidy(() => { - // Zip together scores, box corners, and index - const zipped = []; - for (let i = 0; i < scores.length; i += 1) { - zipped.push([ - scores[i], - [boxes[4 * i], boxes[(4 * i) + 1], boxes[(4 * i) + 2], boxes[(4 * i) + 3]], i, - ]); - } - const sortedBoxes = zipped.sort((a, b) => b[0] - a[0]); - const selectedBoxes = []; - - sortedBoxes.forEach((box) => { - let add = true; - for (let i = 0; i < selectedBoxes.length; i += 1) { - const curIOU = boxIOU(box[1], selectedBoxes[i][1]); - if (curIOU > iouThreshold) { - add = false; - break; - } - } - if (add) { - selectedBoxes.push(box); - } - }); - - return [ - selectedBoxes.map(e => e[2]), - selectedBoxes.map(e => e[1]), - selectedBoxes.map(e => e[0]), - ]; - }) -}; - -// Convert yolo output to bounding box + prob tensors -/* eslint no-param-reassign: 0 */ -export function head(feats, anchors, numClasses) { - return tf.tidy(() => { - const numAnchors = anchors.shape[0]; - - const anchorsTensor = tf.reshape(anchors, [1, 1, numAnchors, 2]); - - let convDims = feats.shape.slice(1, 3); - - // For later use - const convDims0 = convDims[0]; - const convDims1 = convDims[1]; - - let convHeightIndex = tf.range(0, convDims[0]); - let convWidthIndex = tf.range(0, convDims[1]); - convHeightIndex = tf.tile(convHeightIndex, [convDims[1]]); - - convWidthIndex = tf.tile(tf.expandDims(convWidthIndex, 0), [convDims[0], 1]); - convWidthIndex = tf.transpose(convWidthIndex).flatten(); - - let convIndex = tf.transpose(tf.stack([convHeightIndex, convWidthIndex])); - convIndex = tf.reshape(convIndex, [convDims[0], convDims[1], 1, 2]); - convIndex = tf.cast(convIndex, feats.dtype); - - feats = tf.reshape(feats, [convDims[0], convDims[1], numAnchors, numClasses + 5]); - convDims = tf.cast(tf.reshape(tf.tensor1d(convDims), [1, 1, 1, 2]), feats.dtype); - - let boxXY = tf.sigmoid(feats.slice([0, 0, 0, 0], [convDims0, convDims1, numAnchors, 2])); - let boxWH = tf.exp(feats.slice([0, 0, 0, 2], [convDims0, convDims1, numAnchors, 2])); - const boxConfidence = tf.sigmoid(feats.slice([0, 0, 0, 4], [convDims0, convDims1, numAnchors, 1])); - const boxClassProbs = tf.softmax(feats.slice([0, 0, 0, 5], [convDims0, convDims1, numAnchors, numClasses])); - - boxXY = tf.div(tf.add(boxXY, convIndex), convDims); - boxWH = tf.div(tf.mul(boxWH, anchorsTensor), convDims); - - return [boxXY, boxWH, boxConfidence, boxClassProbs]; - }) -} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 9be1e57c7..4ec42aaad 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,8 @@ import YOLO from './ObjectDetector/YOLO'; import objectDetector from './ObjectDetector'; import poseNet from './PoseNet'; import * as imageUtils from './utils/imageUtilities'; +import * as drawUtils from './utils/drawDetections'; + import styleTransfer from './StyleTransfer/'; import charRNN from './CharRNN/'; import pix2pix from './Pix2pix/'; @@ -56,6 +58,7 @@ const withPreload = { module.exports = Object.assign({p5Utils}, preloadRegister(withPreload), { KNNClassifier, ...imageUtils, + ...drawUtils, tf, tfvis, version, diff --git a/src/utils/drawDetections.js b/src/utils/drawDetections.js new file mode 100644 index 000000000..833f6f1b4 --- /dev/null +++ b/src/utils/drawDetections.js @@ -0,0 +1,51 @@ +function map(num, inmin, inmax) { + return ((num - inmin) * 360) / (inmax - inmin); +} + +function hsl(num, min, max) { + return `hsla(${map(num, min, max)}, 100%, 50%,1)`; +} + +function drawObject(item, ctx) { + const lineWidth = 3; + const textBoxHeight = 20; + const label = `${item.label}:${(item.confidence * 100).toFixed(1)}%`; + const textBoxWidth = ctx.measureText(label).width + 25; + // console.log(ctx.measureText(item.label)); + ctx.font = '1rem Segoe UI'; + ctx.lineWidth = lineWidth; + const color = hsl(item.labelIndex, 0, 50); + ctx.strokeStyle = color; + ctx.fillStyle = color; + ctx.beginPath(); + ctx.rect(item.x, item.y, item.w, item.h); + ctx.stroke(); + ctx.fillStyle = color; + const textBoxX = item.x - lineWidth / 2; + const textBoxY = item.y + lineWidth / 2; + if (item.y - 25 >= 0) { + // Box on top + ctx.fillRect(textBoxX, textBoxY, textBoxWidth, -textBoxHeight); + ctx.fillStyle = '#000000'; + ctx.fillText(label, textBoxX + 5, textBoxY - 5); + } else { + // Box Inside + ctx.fillRect(textBoxY, textBoxY, textBoxWidth, textBoxHeight); + ctx.fillStyle = '#000000'; + ctx.fillText(label, textBoxX, textBoxY); + } +} + +function drawDetections(detections, canvas) { + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + for (let index = 0; index < detections.length; index+=1) { + drawObject(detections[index], ctx); + } +} + +export { + drawObject, + drawDetections +} \ No newline at end of file diff --git a/src/utils/imageUtilities.js b/src/utils/imageUtilities.js index 812295c63..5b192f610 100644 --- a/src/utils/imageUtilities.js +++ b/src/utils/imageUtilities.js @@ -134,6 +134,22 @@ function imgToTensor(input, size = null) { }); } +// Static Method: image to tf tensor +function imgToTensorYOLO(input, size = null) { + return tf.tidy(() => { + const image = tf.browser.fromPixels(input); + const imageShape = [image.shape[0], image.shape[1]]; // height, width + const normalized = image.div(255); + let resized = normalized + if (size) { + if (normalized.shape[0] !== size[0] || normalized.shape[1] !== size[1]) { + resized = tf.image.resizeBilinear(normalized, [size[0], size[1]]); + } + } + return [imageShape, resized.expandDims(0)]; + }); +} + function isInstanceOfSupportedElement(subject) { return (subject instanceof HTMLVideoElement || subject instanceof HTMLImageElement @@ -193,5 +209,6 @@ export { imgToTensor, isInstanceOfSupportedElement, flipImage, - imgToPixelArray + imgToPixelArray, + imgToTensorYOLO, }; diff --git a/src/utils/tensorUtilities.js b/src/utils/tensorUtilities.js new file mode 100644 index 000000000..f1aa14053 --- /dev/null +++ b/src/utils/tensorUtilities.js @@ -0,0 +1,13 @@ +// Copyright (c) 2018 ml5 +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +/** + * a small utility check to see if `toBeDetermined` is a `tf.Tensor` or a `tf.Tensor[]` + * @param toBeDetermined `tf.Tensor` || `tf.Tensor[]` + * @returns a `boolean` indicating if it's a `tf.Tensor` or a `tf.Tensor[]` + */ +export default function isTensorArray(toBeDetermined) { + return !toBeDetermined.shape; +}