├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── process-images.js ├── query-matching.js ├── query-missing.js ├── sample-images ├── T000000.jpg ├── T000001.jpg ├── T000002.jpg ├── T000003.jpg ├── T000004.jpg ├── T000005.jpg ├── T000006.jpg ├── T000007.jpg ├── T000008.jpg ├── T000009.jpg ├── T000010.jpg ├── T000011.jpg ├── T000012.jpg ├── T000013.jpg ├── T000014.jpg ├── T000015.jpg ├── T000016.jpg ├── T000017.jpg ├── T000018.jpg ├── T000019.jpg ├── T000020.jpg ├── T000021.jpg ├── T000022.jpg ├── T000023.jpg ├── T000024.jpg ├── T000025.jpg ├── T000026.jpg ├── T000027.jpg ├── T000028.jpg ├── T000029.jpg ├── T000030.jpg ├── T000031.jpg ├── T000032.jpg ├── T000033.jpg ├── T000034.jpg ├── T000035.jpg ├── T000036.jpg ├── T000037.jpg ├── T000038.jpg ├── T000039.jpg ├── T000040.jpg ├── T000041.jpg ├── T000042.jpg ├── T000043.jpg ├── T000044.jpg ├── T000045.jpg ├── T000046.jpg ├── T000047.jpg ├── T000048.jpg ├── T000049.jpg ├── T000050.jpg ├── T000051.jpg ├── T000052.jpg ├── T000053.jpg ├── T000054.jpg ├── T000055.jpg ├── T000056.jpg ├── T000057.jpg ├── T000058.jpg ├── T000059.jpg ├── T000060.jpg ├── T000061.jpg ├── T000062.jpg ├── T000063.jpg ├── T000064.jpg ├── T000065.jpg ├── T000066.jpg ├── T000067.jpg ├── T000068.jpg ├── T000069.jpg ├── T000070.jpg ├── T000071.jpg ├── T000072.jpg ├── T000073.jpg ├── T000074.jpg ├── T000075.jpg ├── T000076.jpg ├── T000077.jpg ├── T000078.jpg ├── T000079.jpg ├── T000080.jpg ├── T000081.jpg ├── T000082.jpg ├── T000083.jpg ├── T000084.jpg ├── T000085.jpg ├── T000086.jpg ├── T000087.jpg ├── T000088.jpg ├── T000089.jpg ├── T000090.jpg ├── T000091.jpg ├── T000092.jpg ├── T000093.jpg ├── T000094.jpg ├── T000095.jpg ├── T000096.jpg ├── T000097.jpg ├── T000098.jpg └── T000099.jpg ├── src ├── common.js ├── dct │ └── dct.js ├── image-signature-js │ ├── LICENSE │ ├── array_util.js │ ├── image_signature.js │ └── nj_util.js ├── intensity │ └── intensity.js ├── phash │ └── phash.js ├── pinecone.js └── sha256.js ├── upsert.js ├── vectorize-missing.js └── vectorize.js /.gitignore: -------------------------------------------------------------------------------- 1 | images/ 2 | .idea/ 3 | node_modules/ 4 | results/ 5 | 6 | sample-images/reformatted/ 7 | sample-images/grown 8 | sample-images/shrunk 9 | sample-images/cropped 10 | 11 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Image Matching Algorithms For Use With KNN Search 2 | ================================================= 3 | 4 | As part of the development of [alt-text.org](https://alt-text.org), it's necessary to perform large-scale 5 | fuzzy matching of images. To accomplish this, we leverage the Pinecone.io 6 | vector database which offers k-nearest-neighbor searches over feature vectors. 7 | While there's been much publicly available research on image similarity algorithms 8 | which use hamming distance comparison algorithms, we found little on leveraging 9 | KNN searches with non-ML based vector production. 10 | 11 | We tested 4 algorithms: 1024 pixel pHash, 1024 frequency discrete cosine transform, 12 | 1024 pixel intensity, and the algorithm proposed in 13 | [this paper](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.104.2585&rep=rep1&type=pdf) 14 | by Wong, Bern, and Goldberg which results in feature vectors of 544 values. 15 | 16 | Pinecone offers 3 distance metrics for measuring vector adjacency: Cosine, Dot Product, 17 | and Euclidean. While we had good reason to believe that Cosine was the only appropriate 18 | metric for the aforementioned algorithms, we decided to test all three for each 19 | algorithm in case we were mistaken. When returning results Pinecone scores each match, with the range depending 20 | on the metric used, and allows setting the max number to return. 21 | 22 | We performed our tests using the first 10,000 JPEG images in the `train_50k_0.zip` file from a 23 | past Meta image matching competition which can be found 24 | [here](https://ai.facebook.com/datasets/disc21-downloads/). The next 2,000 images were used for measuring performance 25 | when the requested image is not in the database. 26 | 27 | For the [alt-text.org](https://alt-text.org) use case, we're primarily interested in matching 28 | almost identical images. Several cases were of particular interest: larger, smaller, cropped, 29 | and in a different image format. 30 | 31 | The choice of JavaScript was forced by the existing codebase, including all the matching algorithm options, 32 | already being in the language. The single-threaded nature of JavaScript was a major disadvantage and contributed 33 | to very long runtimes, but opting for a better suited language would mean a full rewrite of the 34 | [alt-text.org](https://alt-text.org) backend. *Note: since this was written the chosen algorithm has been implemented in [Rust](https://github.com/alt-text-org/image-match-rs)*. 35 | 36 | We ran an initial round of tests collecting information only on whether the top match was correct and the count of 37 | matches with a score over some value. Examining our findings it quickly became clear that we had misunderstood the 38 | score field of returned matches, believing that it would be in [0, 1] for all metrics. These initial results did 39 | however make it apparent that the Dot Product metric was not appropriate for any 40 | algorithm except possibly Goldberg, so it was omitted for other algorithms in the second round of tests. The Euclidean 41 | distance metric performed decently, but had significant issues matching identical images. For lack of time, we chose to 42 | drop it as well for all algorithms. While the results from the first round did not directly influence our findings, 43 | the raw data is available in a Google sheet linked below. 44 | 45 | 46 | Process 47 | ------- 48 | 49 | 1. [process-images.js](process-images.js): For each image, compute and save to disk versions 2x size, 1/2x size, 50 | with a 5% border crop, and in PNG format. We use the Node.js `canvas` library for this, which wraps 51 | the `Cairo` open source image processing library. 52 | 2. [vectorize.js](vectorize.js): For each matching algorithm, compute the feature vector for each image and 53 | all its alterations, as well as the SHA256 hash of the original image, then store the result in a large JSON 54 | blob on disk. 55 | 3. [vectorize-missing.js](vectorize-missing.js): Compute and save to disk vectors for a smaller set of images. These 56 | will be searched for but not inserted. 57 | 4. [upsert.js](upsert.js): Upsert a record to Pinecone for only the vector for the original image, with the image hash 58 | as the stored value. 59 | 5. [query-matching.js](query-matching.js): For each image and all its alterations, perform a KNN query, recording 60 | the metrics discussed below, and then print a summary of the findings in CSV format. 61 | 6. [query-missing.js](query-missing.js): For each missing image, perform a KNN query, recording the tops score and the 62 | total set of scores. 63 | 64 | 65 | Goals 66 | ----- 67 | 68 | Three things were of interest for each algorithm: 69 | 70 | - Vector computation time 71 | - Whether the correct record was the top result for each image and all its alterations 72 | - How reliably non-matching results could be excluded 73 | 74 | 75 | Result Format 76 | ------------- 77 | 78 | The following results for a given `(algorithm, distance metric)` are recorded: 79 | 80 | 1. Vector computation time: average, min, max, and percentiles 81 | 2. What percent of the time was the correct image the top result 82 | 3. What percent of the time was the correct image present in results 83 | 4. The score of the matching image if found: average, min, max, and percentiles 84 | 5. The score of the highest non-matching image: average, min, max, and percentiles 85 | 86 | Findings 87 | -------- 88 | 89 | **Goldberg - Cosine** 90 | - Goldberg with a Dot Product metric consistently fared the worst at almost all measures, and could be excluded 91 | from further consideration 92 | - Algorithm to compute is complex and the implementation available is more so 93 | - The slowest to compute by several orders of magnitude, but the tested implementation may be significantly optimizable 94 | - *Note:* After this document was drafted, we looked into optimizing the algorithm implementation, and were able to 95 | improve it approximately 3x. It appears there may be additional avenues for improvement. 96 | - The least reliable for all but cropped images, which are the least important, but the difference was 97 | mostly within tolerance 98 | - The largest and most reliable difference between matching and non-matching, with a score of 0.6 appearing to be a 99 | reasonably reliable cutoff which accords with the source paper 100 | 101 | **Discrete Cosine Transform - Cosine** 102 | - Compute second slowest, but still very fast 103 | - Perfect correctness for all but cropped images, where it's still very reliable 104 | - No clear choice of a score that would reliably include non-exact matches while excluding others 105 | 106 | **Intensity - Cosine** 107 | - Computation quickest and simplest to understand 108 | - Excellent at matching all but cropped images 109 | - No clear choice of a score that would reliably include non-exact matches while excluding others 110 | 111 | **pHash - Cosine** 112 | - Computation quick and simple to understand 113 | - Excellent at matching all but cropped images 114 | - No clear choice of a score that would reliably include non-exact matches while excluding others 115 | 116 | **Conclusion** 117 | 118 | Based on the findings above, we will endeavor to optimize Goldberg and determine if performance is acceptable. If it is 119 | not, we may still opt to use it. Other algorithms fall down hard on the key question of separating altered but matching 120 | images from searches returning no similar results. 121 | 122 | Data Availability 123 | ----------------- 124 | 125 | Raw results are available in 126 | [this Google sheet](https://docs.google.com/spreadsheets/d/12rxQ4aJhzthBLZ1Z9JEANVloY3u8_rjWPbfIHfQz3Ms/edit?usp=sharing) 127 | 128 | The vast bulk of the runtime for this process is spent computing feature vectors, if you would like to test other KNN 129 | query engines, the precomputed vectors are available upon request. 130 | 131 | Raw results from the first round of tests are available in 132 | [this Google sheet](https://docs.google.com/spreadsheets/d/1Q2TXNwPgB-awFmWzeXYXX21OUVjkt0BU0ldPdtRdxTo/edit?usp=sharing). 133 | 134 | 135 | Future Work 136 | ----------- 137 | 138 | - Testing additional algorithms 139 | - Would Goldberg perform better or worse with a wider lightness scale? 140 | - Testing images with a watermark or text added 141 | - Reformatted images went from JPEG to PNG as those were the formats available. The performance matching those was 142 | identical to matching identical images, hinting that we really needed to be going the other direction given that 143 | JPEG is lossy. 144 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algo-test-harness", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@mapbox/node-pre-gyp": { 8 | "version": "1.0.9", 9 | "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", 10 | "integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==", 11 | "requires": { 12 | "detect-libc": "^2.0.0", 13 | "https-proxy-agent": "^5.0.0", 14 | "make-dir": "^3.1.0", 15 | "node-fetch": "^2.6.7", 16 | "nopt": "^5.0.0", 17 | "npmlog": "^5.0.1", 18 | "rimraf": "^3.0.2", 19 | "semver": "^7.3.5", 20 | "tar": "^6.1.11" 21 | }, 22 | "dependencies": { 23 | "ansi-regex": { 24 | "version": "5.0.1", 25 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 26 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 27 | }, 28 | "are-we-there-yet": { 29 | "version": "2.0.0", 30 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", 31 | "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", 32 | "requires": { 33 | "delegates": "^1.0.0", 34 | "readable-stream": "^3.6.0" 35 | } 36 | }, 37 | "detect-libc": { 38 | "version": "2.0.1", 39 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", 40 | "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" 41 | }, 42 | "gauge": { 43 | "version": "3.0.2", 44 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", 45 | "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", 46 | "requires": { 47 | "aproba": "^1.0.3 || ^2.0.0", 48 | "color-support": "^1.1.2", 49 | "console-control-strings": "^1.0.0", 50 | "has-unicode": "^2.0.1", 51 | "object-assign": "^4.1.1", 52 | "signal-exit": "^3.0.0", 53 | "string-width": "^4.2.3", 54 | "strip-ansi": "^6.0.1", 55 | "wide-align": "^1.1.2" 56 | } 57 | }, 58 | "is-fullwidth-code-point": { 59 | "version": "3.0.0", 60 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 61 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 62 | }, 63 | "node-fetch": { 64 | "version": "2.6.7", 65 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 66 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 67 | "requires": { 68 | "whatwg-url": "^5.0.0" 69 | } 70 | }, 71 | "npmlog": { 72 | "version": "5.0.1", 73 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", 74 | "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", 75 | "requires": { 76 | "are-we-there-yet": "^2.0.0", 77 | "console-control-strings": "^1.1.0", 78 | "gauge": "^3.0.0", 79 | "set-blocking": "^2.0.0" 80 | } 81 | }, 82 | "readable-stream": { 83 | "version": "3.6.0", 84 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 85 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 86 | "requires": { 87 | "inherits": "^2.0.3", 88 | "string_decoder": "^1.1.1", 89 | "util-deprecate": "^1.0.1" 90 | } 91 | }, 92 | "string-width": { 93 | "version": "4.2.3", 94 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 95 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 96 | "requires": { 97 | "emoji-regex": "^8.0.0", 98 | "is-fullwidth-code-point": "^3.0.0", 99 | "strip-ansi": "^6.0.1" 100 | } 101 | }, 102 | "strip-ansi": { 103 | "version": "6.0.1", 104 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 105 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 106 | "requires": { 107 | "ansi-regex": "^5.0.1" 108 | } 109 | } 110 | } 111 | }, 112 | "JSONStream": { 113 | "version": "1.3.5", 114 | "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", 115 | "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", 116 | "requires": { 117 | "jsonparse": "^1.2.0", 118 | "through": ">=2.2.7 <3" 119 | } 120 | }, 121 | "abbrev": { 122 | "version": "1.1.1", 123 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 124 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 125 | }, 126 | "acorn": { 127 | "version": "7.4.1", 128 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 129 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" 130 | }, 131 | "agent-base": { 132 | "version": "6.0.2", 133 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 134 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 135 | "requires": { 136 | "debug": "4" 137 | } 138 | }, 139 | "align-text": { 140 | "version": "0.1.4", 141 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", 142 | "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", 143 | "requires": { 144 | "kind-of": "^3.0.2", 145 | "longest": "^1.0.1", 146 | "repeat-string": "^1.5.2" 147 | } 148 | }, 149 | "amdefine": { 150 | "version": "1.0.1", 151 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 152 | "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", 153 | "optional": true 154 | }, 155 | "ansi-regex": { 156 | "version": "2.1.1", 157 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 158 | "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" 159 | }, 160 | "aproba": { 161 | "version": "1.2.0", 162 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 163 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 164 | }, 165 | "are-we-there-yet": { 166 | "version": "1.1.7", 167 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", 168 | "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", 169 | "requires": { 170 | "delegates": "^1.0.0", 171 | "readable-stream": "^2.0.6" 172 | }, 173 | "dependencies": { 174 | "readable-stream": { 175 | "version": "2.3.7", 176 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 177 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 178 | "requires": { 179 | "core-util-is": "~1.0.0", 180 | "inherits": "~2.0.3", 181 | "isarray": "~1.0.0", 182 | "process-nextick-args": "~2.0.0", 183 | "safe-buffer": "~5.1.1", 184 | "string_decoder": "~1.1.1", 185 | "util-deprecate": "~1.0.1" 186 | } 187 | } 188 | } 189 | }, 190 | "array-flatten": { 191 | "version": "3.0.0", 192 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", 193 | "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" 194 | }, 195 | "balanced-match": { 196 | "version": "1.0.2", 197 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 198 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 199 | }, 200 | "base64-js": { 201 | "version": "1.5.1", 202 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 203 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 204 | }, 205 | "bindings": { 206 | "version": "1.5.0", 207 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 208 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 209 | "requires": { 210 | "file-uri-to-path": "1.0.0" 211 | } 212 | }, 213 | "bit-twiddle": { 214 | "version": "1.0.2", 215 | "resolved": "https://registry.npmjs.org/bit-twiddle/-/bit-twiddle-1.0.2.tgz", 216 | "integrity": "sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==" 217 | }, 218 | "bl": { 219 | "version": "4.1.0", 220 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 221 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 222 | "requires": { 223 | "buffer": "^5.5.0", 224 | "inherits": "^2.0.4", 225 | "readable-stream": "^3.4.0" 226 | }, 227 | "dependencies": { 228 | "readable-stream": { 229 | "version": "3.6.0", 230 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 231 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 232 | "requires": { 233 | "inherits": "^2.0.3", 234 | "string_decoder": "^1.1.1", 235 | "util-deprecate": "^1.0.1" 236 | } 237 | } 238 | } 239 | }, 240 | "brace-expansion": { 241 | "version": "1.1.11", 242 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 243 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 244 | "requires": { 245 | "balanced-match": "^1.0.0", 246 | "concat-map": "0.0.1" 247 | } 248 | }, 249 | "buffer": { 250 | "version": "5.7.1", 251 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 252 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 253 | "requires": { 254 | "base64-js": "^1.3.1", 255 | "ieee754": "^1.1.13" 256 | } 257 | }, 258 | "buffer-from": { 259 | "version": "1.1.2", 260 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 261 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 262 | }, 263 | "camelcase": { 264 | "version": "1.2.1", 265 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 266 | "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==" 267 | }, 268 | "canvas": { 269 | "version": "2.9.3", 270 | "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.3.tgz", 271 | "integrity": "sha512-WOUM7ghii5TV2rbhaZkh1youv/vW1/Canev6Yx6BG2W+1S07w8jKZqKkPnbiPpQEDsnJdN8ouDd7OvQEGXDcUw==", 272 | "requires": { 273 | "@mapbox/node-pre-gyp": "^1.0.0", 274 | "nan": "^2.15.0", 275 | "simple-get": "^3.0.3" 276 | }, 277 | "dependencies": { 278 | "simple-get": { 279 | "version": "3.1.1", 280 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", 281 | "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", 282 | "requires": { 283 | "decompress-response": "^4.2.0", 284 | "once": "^1.3.1", 285 | "simple-concat": "^1.0.0" 286 | } 287 | } 288 | } 289 | }, 290 | "center-align": { 291 | "version": "0.1.3", 292 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", 293 | "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", 294 | "requires": { 295 | "align-text": "^0.1.3", 296 | "lazy-cache": "^1.0.3" 297 | } 298 | }, 299 | "chownr": { 300 | "version": "1.1.4", 301 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 302 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 303 | }, 304 | "cliui": { 305 | "version": "2.1.0", 306 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", 307 | "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", 308 | "requires": { 309 | "center-align": "^0.1.1", 310 | "right-align": "^0.1.1", 311 | "wordwrap": "0.0.2" 312 | } 313 | }, 314 | "code-point-at": { 315 | "version": "1.1.0", 316 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 317 | "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==" 318 | }, 319 | "color": { 320 | "version": "3.2.1", 321 | "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", 322 | "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", 323 | "requires": { 324 | "color-convert": "^1.9.3", 325 | "color-string": "^1.6.0" 326 | } 327 | }, 328 | "color-convert": { 329 | "version": "1.9.3", 330 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 331 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 332 | "requires": { 333 | "color-name": "1.1.3" 334 | } 335 | }, 336 | "color-name": { 337 | "version": "1.1.3", 338 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 339 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 340 | }, 341 | "color-string": { 342 | "version": "1.9.1", 343 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 344 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 345 | "requires": { 346 | "color-name": "^1.0.0", 347 | "simple-swizzle": "^0.2.2" 348 | } 349 | }, 350 | "color-support": { 351 | "version": "1.1.3", 352 | "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", 353 | "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" 354 | }, 355 | "concat-map": { 356 | "version": "0.0.1", 357 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 358 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 359 | }, 360 | "concat-stream": { 361 | "version": "1.6.2", 362 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 363 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 364 | "requires": { 365 | "buffer-from": "^1.0.0", 366 | "inherits": "^2.0.3", 367 | "readable-stream": "^2.2.2", 368 | "typedarray": "^0.0.6" 369 | }, 370 | "dependencies": { 371 | "readable-stream": { 372 | "version": "2.3.7", 373 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 374 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 375 | "requires": { 376 | "core-util-is": "~1.0.0", 377 | "inherits": "~2.0.3", 378 | "isarray": "~1.0.0", 379 | "process-nextick-args": "~2.0.0", 380 | "safe-buffer": "~5.1.1", 381 | "string_decoder": "~1.1.1", 382 | "util-deprecate": "~1.0.1" 383 | } 384 | } 385 | } 386 | }, 387 | "console-control-strings": { 388 | "version": "1.1.0", 389 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 390 | "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" 391 | }, 392 | "core-util-is": { 393 | "version": "1.0.3", 394 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 395 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 396 | }, 397 | "cwise": { 398 | "version": "1.0.10", 399 | "resolved": "https://registry.npmjs.org/cwise/-/cwise-1.0.10.tgz", 400 | "integrity": "sha512-4OQ6FXVTRO2bk/OkIEt0rNqDk63aOv3Siny6ZD2/WN9CH7k8X6XyQdcip4zKg1WG+L8GP5t2zicXbDb+H7Y77Q==", 401 | "requires": { 402 | "cwise-compiler": "^1.1.1", 403 | "cwise-parser": "^1.0.0", 404 | "static-module": "^1.0.0", 405 | "uglify-js": "^2.6.0" 406 | } 407 | }, 408 | "cwise-compiler": { 409 | "version": "1.1.3", 410 | "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz", 411 | "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==", 412 | "requires": { 413 | "uniq": "^1.0.0" 414 | } 415 | }, 416 | "cwise-parser": { 417 | "version": "1.0.3", 418 | "resolved": "https://registry.npmjs.org/cwise-parser/-/cwise-parser-1.0.3.tgz", 419 | "integrity": "sha512-nAe238ctwjt9l5exq9CQkHS1Tj6YRGAQxqfL4VaN1B2oqG1Ss0VVqIrBG/vyOgN301PI22wL6ZIhe/zA+BO56Q==", 420 | "requires": { 421 | "esprima": "^1.0.3", 422 | "uniq": "^1.0.0" 423 | } 424 | }, 425 | "deasync": { 426 | "version": "0.1.28", 427 | "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.28.tgz", 428 | "integrity": "sha512-QqLF6inIDwiATrfROIyQtwOQxjZuek13WRYZ7donU5wJPLoP67MnYxA6QtqdvdBy2mMqv5m3UefBVdJjvevOYg==", 429 | "requires": { 430 | "bindings": "^1.5.0", 431 | "node-addon-api": "^1.7.1" 432 | } 433 | }, 434 | "debug": { 435 | "version": "4.3.4", 436 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 437 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 438 | "requires": { 439 | "ms": "2.1.2" 440 | } 441 | }, 442 | "decamelize": { 443 | "version": "1.2.0", 444 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 445 | "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" 446 | }, 447 | "decompress-response": { 448 | "version": "4.2.1", 449 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", 450 | "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", 451 | "requires": { 452 | "mimic-response": "^2.0.0" 453 | } 454 | }, 455 | "deep-extend": { 456 | "version": "0.6.0", 457 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 458 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 459 | }, 460 | "delegates": { 461 | "version": "1.0.0", 462 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 463 | "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" 464 | }, 465 | "detect-libc": { 466 | "version": "1.0.3", 467 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 468 | "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==" 469 | }, 470 | "dup": { 471 | "version": "1.0.0", 472 | "resolved": "https://registry.npmjs.org/dup/-/dup-1.0.0.tgz", 473 | "integrity": "sha512-Bz5jxMMC0wgp23Zm15ip1x8IhYRqJvF3nFC0UInJUDkN1z4uNPk9jTnfCUJXbOGiQ1JbXLQsiV41Fb+HXcj5BA==" 474 | }, 475 | "duplexer2": { 476 | "version": "0.0.2", 477 | "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", 478 | "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", 479 | "requires": { 480 | "readable-stream": "~1.1.9" 481 | }, 482 | "dependencies": { 483 | "isarray": { 484 | "version": "0.0.1", 485 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 486 | "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" 487 | }, 488 | "readable-stream": { 489 | "version": "1.1.14", 490 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 491 | "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", 492 | "requires": { 493 | "core-util-is": "~1.0.0", 494 | "inherits": "~2.0.1", 495 | "isarray": "0.0.1", 496 | "string_decoder": "~0.10.x" 497 | } 498 | }, 499 | "string_decoder": { 500 | "version": "0.10.31", 501 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 502 | "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" 503 | } 504 | } 505 | }, 506 | "emoji-regex": { 507 | "version": "8.0.0", 508 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 509 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 510 | }, 511 | "end-of-stream": { 512 | "version": "1.4.4", 513 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 514 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 515 | "requires": { 516 | "once": "^1.4.0" 517 | } 518 | }, 519 | "escodegen": { 520 | "version": "1.3.3", 521 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", 522 | "integrity": "sha512-z9FWgKc48wjMlpzF5ymKS1AF8OIgnKLp9VyN7KbdtyrP/9lndwUFqCtMm+TAJmJf7KJFFYc4cFJfVTTGkKEwsA==", 523 | "requires": { 524 | "esprima": "~1.1.1", 525 | "estraverse": "~1.5.0", 526 | "esutils": "~1.0.0", 527 | "source-map": "~0.1.33" 528 | }, 529 | "dependencies": { 530 | "esprima": { 531 | "version": "1.1.1", 532 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", 533 | "integrity": "sha512-qxxB994/7NtERxgXdFgLHIs9M6bhLXc6qtUmWZ3L8+gTQ9qaoyki2887P2IqAYsoENyr8SUbTutStDniOHSDHg==" 534 | } 535 | } 536 | }, 537 | "esprima": { 538 | "version": "1.2.5", 539 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", 540 | "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==" 541 | }, 542 | "estraverse": { 543 | "version": "1.5.1", 544 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", 545 | "integrity": "sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==" 546 | }, 547 | "esutils": { 548 | "version": "1.0.0", 549 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", 550 | "integrity": "sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==" 551 | }, 552 | "expand-template": { 553 | "version": "2.0.3", 554 | "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", 555 | "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" 556 | }, 557 | "falafel": { 558 | "version": "2.2.5", 559 | "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.5.tgz", 560 | "integrity": "sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==", 561 | "requires": { 562 | "acorn": "^7.1.1", 563 | "isarray": "^2.0.1" 564 | }, 565 | "dependencies": { 566 | "isarray": { 567 | "version": "2.0.5", 568 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 569 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" 570 | } 571 | } 572 | }, 573 | "file-uri-to-path": { 574 | "version": "1.0.0", 575 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 576 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 577 | }, 578 | "fs-constants": { 579 | "version": "1.0.0", 580 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 581 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 582 | }, 583 | "fs-minipass": { 584 | "version": "2.1.0", 585 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 586 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 587 | "requires": { 588 | "minipass": "^3.0.0" 589 | } 590 | }, 591 | "fs.realpath": { 592 | "version": "1.0.0", 593 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 594 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 595 | }, 596 | "function-bind": { 597 | "version": "1.1.1", 598 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 599 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 600 | }, 601 | "gauge": { 602 | "version": "2.7.4", 603 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 604 | "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", 605 | "requires": { 606 | "aproba": "^1.0.3", 607 | "console-control-strings": "^1.0.0", 608 | "has-unicode": "^2.0.0", 609 | "object-assign": "^4.1.0", 610 | "signal-exit": "^3.0.0", 611 | "string-width": "^1.0.1", 612 | "strip-ansi": "^3.0.1", 613 | "wide-align": "^1.1.0" 614 | } 615 | }, 616 | "github-from-package": { 617 | "version": "0.0.0", 618 | "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", 619 | "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" 620 | }, 621 | "glob": { 622 | "version": "7.2.3", 623 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 624 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 625 | "requires": { 626 | "fs.realpath": "^1.0.0", 627 | "inflight": "^1.0.4", 628 | "inherits": "2", 629 | "minimatch": "^3.1.1", 630 | "once": "^1.3.0", 631 | "path-is-absolute": "^1.0.0" 632 | } 633 | }, 634 | "has": { 635 | "version": "1.0.3", 636 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 637 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 638 | "requires": { 639 | "function-bind": "^1.1.1" 640 | } 641 | }, 642 | "has-unicode": { 643 | "version": "2.0.1", 644 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 645 | "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" 646 | }, 647 | "https-proxy-agent": { 648 | "version": "5.0.1", 649 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 650 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 651 | "requires": { 652 | "agent-base": "6", 653 | "debug": "4" 654 | } 655 | }, 656 | "ieee754": { 657 | "version": "1.2.1", 658 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 659 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 660 | }, 661 | "inflight": { 662 | "version": "1.0.6", 663 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 664 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 665 | "requires": { 666 | "once": "^1.3.0", 667 | "wrappy": "1" 668 | } 669 | }, 670 | "inherits": { 671 | "version": "2.0.4", 672 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 673 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 674 | }, 675 | "ini": { 676 | "version": "1.3.8", 677 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", 678 | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" 679 | }, 680 | "iota-array": { 681 | "version": "1.0.0", 682 | "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", 683 | "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==" 684 | }, 685 | "is-arrayish": { 686 | "version": "0.3.2", 687 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 688 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 689 | }, 690 | "is-buffer": { 691 | "version": "1.1.6", 692 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 693 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 694 | }, 695 | "is-fullwidth-code-point": { 696 | "version": "1.0.0", 697 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 698 | "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", 699 | "requires": { 700 | "number-is-nan": "^1.0.0" 701 | } 702 | }, 703 | "isarray": { 704 | "version": "1.0.0", 705 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 706 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 707 | }, 708 | "jsonparse": { 709 | "version": "1.3.1", 710 | "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", 711 | "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==" 712 | }, 713 | "kind-of": { 714 | "version": "3.2.2", 715 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 716 | "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", 717 | "requires": { 718 | "is-buffer": "^1.1.5" 719 | } 720 | }, 721 | "lazy-cache": { 722 | "version": "1.0.4", 723 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 724 | "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" 725 | }, 726 | "lodash": { 727 | "version": "4.17.21", 728 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 729 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 730 | }, 731 | "longest": { 732 | "version": "1.0.1", 733 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", 734 | "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==" 735 | }, 736 | "lru-cache": { 737 | "version": "6.0.0", 738 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 739 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 740 | "requires": { 741 | "yallist": "^4.0.0" 742 | } 743 | }, 744 | "make-dir": { 745 | "version": "3.1.0", 746 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 747 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 748 | "requires": { 749 | "semver": "^6.0.0" 750 | }, 751 | "dependencies": { 752 | "semver": { 753 | "version": "6.3.0", 754 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 755 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 756 | } 757 | } 758 | }, 759 | "mimic-response": { 760 | "version": "2.1.0", 761 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", 762 | "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" 763 | }, 764 | "minimatch": { 765 | "version": "3.1.2", 766 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 767 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 768 | "requires": { 769 | "brace-expansion": "^1.1.7" 770 | } 771 | }, 772 | "minimist": { 773 | "version": "0.0.8", 774 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 775 | "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==" 776 | }, 777 | "minipass": { 778 | "version": "3.3.4", 779 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", 780 | "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", 781 | "requires": { 782 | "yallist": "^4.0.0" 783 | } 784 | }, 785 | "minizlib": { 786 | "version": "2.1.2", 787 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 788 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 789 | "requires": { 790 | "minipass": "^3.0.0", 791 | "yallist": "^4.0.0" 792 | } 793 | }, 794 | "mkdirp": { 795 | "version": "1.0.4", 796 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 797 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 798 | }, 799 | "mkdirp-classic": { 800 | "version": "0.5.3", 801 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 802 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 803 | }, 804 | "ms": { 805 | "version": "2.1.2", 806 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 807 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 808 | }, 809 | "nan": { 810 | "version": "2.16.0", 811 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", 812 | "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==" 813 | }, 814 | "napi-build-utils": { 815 | "version": "1.0.2", 816 | "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", 817 | "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" 818 | }, 819 | "ndarray": { 820 | "version": "1.0.19", 821 | "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz", 822 | "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==", 823 | "requires": { 824 | "iota-array": "^1.0.0", 825 | "is-buffer": "^1.0.2" 826 | } 827 | }, 828 | "ndarray-fft": { 829 | "version": "1.0.3", 830 | "resolved": "https://registry.npmjs.org/ndarray-fft/-/ndarray-fft-1.0.3.tgz", 831 | "integrity": "sha512-p7OPcNAHP616TdoQdmroW666To530jY1q32Gy1DvK3fkaAQ4BuGu715UDDPIARkVQGhHC2qhbjwrhYG2eUQPCw==", 832 | "requires": { 833 | "bit-twiddle": "^1.0.2", 834 | "cwise": "^1.0.4", 835 | "ndarray": "^1.0.15", 836 | "ndarray-ops": "^1.2.2", 837 | "typedarray-pool": "^1.0.0" 838 | } 839 | }, 840 | "ndarray-gemm": { 841 | "version": "1.0.0", 842 | "resolved": "https://registry.npmjs.org/ndarray-gemm/-/ndarray-gemm-1.0.0.tgz", 843 | "integrity": "sha512-LSAzu9dFrQHGImnO/14EtKuRsxQwyehtYg56mxajTB2XnJ4eVx90Dq+xP2x9lyH4PLPtVnZMhGrvnHiIxtGysw==" 844 | }, 845 | "ndarray-ops": { 846 | "version": "1.2.2", 847 | "resolved": "https://registry.npmjs.org/ndarray-ops/-/ndarray-ops-1.2.2.tgz", 848 | "integrity": "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw==", 849 | "requires": { 850 | "cwise-compiler": "^1.0.0" 851 | } 852 | }, 853 | "node-abi": { 854 | "version": "2.30.1", 855 | "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", 856 | "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", 857 | "requires": { 858 | "semver": "^5.4.1" 859 | }, 860 | "dependencies": { 861 | "semver": { 862 | "version": "5.7.1", 863 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 864 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 865 | } 866 | } 867 | }, 868 | "node-addon-api": { 869 | "version": "1.7.2", 870 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", 871 | "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" 872 | }, 873 | "node-fetch": { 874 | "version": "2.6.7", 875 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 876 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 877 | "requires": { 878 | "whatwg-url": "^5.0.0" 879 | } 880 | }, 881 | "nopt": { 882 | "version": "5.0.0", 883 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 884 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 885 | "requires": { 886 | "abbrev": "1" 887 | } 888 | }, 889 | "npmlog": { 890 | "version": "4.1.2", 891 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 892 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 893 | "requires": { 894 | "are-we-there-yet": "~1.1.2", 895 | "console-control-strings": "~1.1.0", 896 | "gauge": "~2.7.3", 897 | "set-blocking": "~2.0.0" 898 | } 899 | }, 900 | "number-is-nan": { 901 | "version": "1.0.1", 902 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 903 | "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==" 904 | }, 905 | "numjs": { 906 | "version": "0.16.1", 907 | "resolved": "https://registry.npmjs.org/numjs/-/numjs-0.16.1.tgz", 908 | "integrity": "sha512-0Y1T0WZZRyST3mCIHjTUEo6EZHVnUSh7DD1d3tytQjrbNx5Ib7yKItjMtsxBdULH02OsKSNbqZgbK+4cxmdNrA==", 909 | "requires": { 910 | "cwise": "^1.0.8", 911 | "deasync": "^0.1.21", 912 | "lodash": "^4.17.21", 913 | "ndarray": "^1.0.18", 914 | "ndarray-fft": "^1.0.0", 915 | "ndarray-gemm": "^1.0.0", 916 | "ndarray-ops": "^1.2.2", 917 | "sharp": "^0.27.2", 918 | "typedarray-pool": "^1.1.0" 919 | } 920 | }, 921 | "object-assign": { 922 | "version": "4.1.1", 923 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 924 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" 925 | }, 926 | "object-inspect": { 927 | "version": "0.4.0", 928 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-0.4.0.tgz", 929 | "integrity": "sha512-8WvkvUZiKAjjsy/63rJjA7jw9uyF0CLVLjBKEfnPHE3Jxvs1LgwqL2OmJN+LliIX1vrzKW+AAu02Cc+xv27ncQ==" 930 | }, 931 | "object-keys": { 932 | "version": "0.4.0", 933 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", 934 | "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==" 935 | }, 936 | "once": { 937 | "version": "1.4.0", 938 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 939 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 940 | "requires": { 941 | "wrappy": "1" 942 | } 943 | }, 944 | "path-is-absolute": { 945 | "version": "1.0.1", 946 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 947 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 948 | }, 949 | "prebuild-install": { 950 | "version": "6.1.4", 951 | "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", 952 | "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", 953 | "requires": { 954 | "detect-libc": "^1.0.3", 955 | "expand-template": "^2.0.3", 956 | "github-from-package": "0.0.0", 957 | "minimist": "^1.2.3", 958 | "mkdirp-classic": "^0.5.3", 959 | "napi-build-utils": "^1.0.1", 960 | "node-abi": "^2.21.0", 961 | "npmlog": "^4.0.1", 962 | "pump": "^3.0.0", 963 | "rc": "^1.2.7", 964 | "simple-get": "^3.0.3", 965 | "tar-fs": "^2.0.0", 966 | "tunnel-agent": "^0.6.0" 967 | }, 968 | "dependencies": { 969 | "minimist": { 970 | "version": "1.2.6", 971 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 972 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 973 | }, 974 | "simple-get": { 975 | "version": "3.1.1", 976 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", 977 | "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", 978 | "requires": { 979 | "decompress-response": "^4.2.0", 980 | "once": "^1.3.1", 981 | "simple-concat": "^1.0.0" 982 | } 983 | } 984 | } 985 | }, 986 | "process-nextick-args": { 987 | "version": "2.0.1", 988 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 989 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 990 | }, 991 | "pump": { 992 | "version": "3.0.0", 993 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 994 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 995 | "requires": { 996 | "end-of-stream": "^1.1.0", 997 | "once": "^1.3.1" 998 | } 999 | }, 1000 | "quote-stream": { 1001 | "version": "0.0.0", 1002 | "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-0.0.0.tgz", 1003 | "integrity": "sha512-m4VtvjAMx00wgAS6eOy50ZDat1EBQeFKBIrtF/oxUt0MenEI33y7runJcRiOihc+JBBIt2aFFJhILIh4e9shJA==", 1004 | "requires": { 1005 | "minimist": "0.0.8", 1006 | "through2": "~0.4.1" 1007 | } 1008 | }, 1009 | "rc": { 1010 | "version": "1.2.8", 1011 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1012 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1013 | "requires": { 1014 | "deep-extend": "^0.6.0", 1015 | "ini": "~1.3.0", 1016 | "minimist": "^1.2.0", 1017 | "strip-json-comments": "~2.0.1" 1018 | }, 1019 | "dependencies": { 1020 | "minimist": { 1021 | "version": "1.2.6", 1022 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", 1023 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 1024 | } 1025 | } 1026 | }, 1027 | "readable-stream": { 1028 | "version": "1.0.34", 1029 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", 1030 | "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", 1031 | "requires": { 1032 | "core-util-is": "~1.0.0", 1033 | "inherits": "~2.0.1", 1034 | "isarray": "0.0.1", 1035 | "string_decoder": "~0.10.x" 1036 | }, 1037 | "dependencies": { 1038 | "isarray": { 1039 | "version": "0.0.1", 1040 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 1041 | "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" 1042 | }, 1043 | "string_decoder": { 1044 | "version": "0.10.31", 1045 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 1046 | "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" 1047 | } 1048 | } 1049 | }, 1050 | "repeat-string": { 1051 | "version": "1.6.1", 1052 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 1053 | "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" 1054 | }, 1055 | "right-align": { 1056 | "version": "0.1.3", 1057 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", 1058 | "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", 1059 | "requires": { 1060 | "align-text": "^0.1.1" 1061 | } 1062 | }, 1063 | "rimraf": { 1064 | "version": "3.0.2", 1065 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1066 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1067 | "requires": { 1068 | "glob": "^7.1.3" 1069 | } 1070 | }, 1071 | "safe-buffer": { 1072 | "version": "5.1.2", 1073 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1074 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1075 | }, 1076 | "semver": { 1077 | "version": "7.3.7", 1078 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", 1079 | "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", 1080 | "requires": { 1081 | "lru-cache": "^6.0.0" 1082 | } 1083 | }, 1084 | "set-blocking": { 1085 | "version": "2.0.0", 1086 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1087 | "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" 1088 | }, 1089 | "shallow-copy": { 1090 | "version": "0.0.1", 1091 | "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", 1092 | "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" 1093 | }, 1094 | "sharp": { 1095 | "version": "0.27.2", 1096 | "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.27.2.tgz", 1097 | "integrity": "sha512-w3FVoONPG/x5MXCc3wsjOS+b9h3CI60qkus6EPQU4dkT0BDm0PyGhDCK6KhtfT3/vbeOMOXAKFNSw+I3QGWkMA==", 1098 | "requires": { 1099 | "array-flatten": "^3.0.0", 1100 | "color": "^3.1.3", 1101 | "detect-libc": "^1.0.3", 1102 | "node-addon-api": "^3.1.0", 1103 | "npmlog": "^4.1.2", 1104 | "prebuild-install": "^6.0.1", 1105 | "semver": "^7.3.4", 1106 | "simple-get": "^4.0.0", 1107 | "tar-fs": "^2.1.1", 1108 | "tunnel-agent": "^0.6.0" 1109 | }, 1110 | "dependencies": { 1111 | "node-addon-api": { 1112 | "version": "3.2.1", 1113 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", 1114 | "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" 1115 | } 1116 | } 1117 | }, 1118 | "signal-exit": { 1119 | "version": "3.0.7", 1120 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 1121 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" 1122 | }, 1123 | "simple-concat": { 1124 | "version": "1.0.1", 1125 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 1126 | "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" 1127 | }, 1128 | "simple-get": { 1129 | "version": "4.0.1", 1130 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", 1131 | "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", 1132 | "requires": { 1133 | "decompress-response": "^6.0.0", 1134 | "once": "^1.3.1", 1135 | "simple-concat": "^1.0.0" 1136 | }, 1137 | "dependencies": { 1138 | "decompress-response": { 1139 | "version": "6.0.0", 1140 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 1141 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 1142 | "requires": { 1143 | "mimic-response": "^3.1.0" 1144 | } 1145 | }, 1146 | "mimic-response": { 1147 | "version": "3.1.0", 1148 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 1149 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" 1150 | } 1151 | } 1152 | }, 1153 | "simple-swizzle": { 1154 | "version": "0.2.2", 1155 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 1156 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 1157 | "requires": { 1158 | "is-arrayish": "^0.3.1" 1159 | } 1160 | }, 1161 | "source-map": { 1162 | "version": "0.1.43", 1163 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 1164 | "integrity": "sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==", 1165 | "optional": true, 1166 | "requires": { 1167 | "amdefine": ">=0.0.4" 1168 | } 1169 | }, 1170 | "static-eval": { 1171 | "version": "0.2.4", 1172 | "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-0.2.4.tgz", 1173 | "integrity": "sha512-6dWWPfa/0+1zULdQi7ssT5EQZHsGK8LygBzhE/HdafNCo4e/Ibt7vLPfxBw9VcdVV+t0ARtN4ZAJKtApVc0A5Q==", 1174 | "requires": { 1175 | "escodegen": "~0.0.24" 1176 | }, 1177 | "dependencies": { 1178 | "escodegen": { 1179 | "version": "0.0.28", 1180 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.28.tgz", 1181 | "integrity": "sha512-6ioQhg16lFs5c7XJlJFXIDxBjO4yRvXC9yK6dLNNGuhI3a/fJukHanPF6qtpjGDgAFzI8Wuq3PSIarWmaOq/5A==", 1182 | "requires": { 1183 | "esprima": "~1.0.2", 1184 | "estraverse": "~1.3.0", 1185 | "source-map": ">= 0.1.2" 1186 | } 1187 | }, 1188 | "esprima": { 1189 | "version": "1.0.4", 1190 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", 1191 | "integrity": "sha512-rp5dMKN8zEs9dfi9g0X1ClLmV//WRyk/R15mppFNICIFRG5P92VP7Z04p8pk++gABo9W2tY+kHyu6P1mEHgmTA==" 1192 | }, 1193 | "estraverse": { 1194 | "version": "1.3.2", 1195 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.3.2.tgz", 1196 | "integrity": "sha512-OkbCPVUu8D9tbsLcUR+CKFRBbhZlogmkbWaP3BPERlkqzWL5Q6IdTz6eUk+b5cid2MTaCqJb2nNRGoJ8TpfPrg==" 1197 | } 1198 | } 1199 | }, 1200 | "static-module": { 1201 | "version": "1.5.0", 1202 | "resolved": "https://registry.npmjs.org/static-module/-/static-module-1.5.0.tgz", 1203 | "integrity": "sha512-XTj7pQOHT33l77lK/Pu8UXqzI44C6LYAqwAc9hLTTESHRqJAFudBpReuopFPpoRr5CtOoSmGfFQC6FPlbDnyCw==", 1204 | "requires": { 1205 | "concat-stream": "~1.6.0", 1206 | "duplexer2": "~0.0.2", 1207 | "escodegen": "~1.3.2", 1208 | "falafel": "^2.1.0", 1209 | "has": "^1.0.0", 1210 | "object-inspect": "~0.4.0", 1211 | "quote-stream": "~0.0.0", 1212 | "readable-stream": "~1.0.27-1", 1213 | "shallow-copy": "~0.0.1", 1214 | "static-eval": "~0.2.0", 1215 | "through2": "~0.4.1" 1216 | } 1217 | }, 1218 | "string-width": { 1219 | "version": "1.0.2", 1220 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1221 | "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", 1222 | "requires": { 1223 | "code-point-at": "^1.0.0", 1224 | "is-fullwidth-code-point": "^1.0.0", 1225 | "strip-ansi": "^3.0.0" 1226 | } 1227 | }, 1228 | "string_decoder": { 1229 | "version": "1.1.1", 1230 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1231 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1232 | "requires": { 1233 | "safe-buffer": "~5.1.0" 1234 | } 1235 | }, 1236 | "strip-ansi": { 1237 | "version": "3.0.1", 1238 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1239 | "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", 1240 | "requires": { 1241 | "ansi-regex": "^2.0.0" 1242 | } 1243 | }, 1244 | "strip-json-comments": { 1245 | "version": "2.0.1", 1246 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1247 | "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" 1248 | }, 1249 | "tar": { 1250 | "version": "6.1.11", 1251 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", 1252 | "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", 1253 | "requires": { 1254 | "chownr": "^2.0.0", 1255 | "fs-minipass": "^2.0.0", 1256 | "minipass": "^3.0.0", 1257 | "minizlib": "^2.1.1", 1258 | "mkdirp": "^1.0.3", 1259 | "yallist": "^4.0.0" 1260 | }, 1261 | "dependencies": { 1262 | "chownr": { 1263 | "version": "2.0.0", 1264 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 1265 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 1266 | } 1267 | } 1268 | }, 1269 | "tar-fs": { 1270 | "version": "2.1.1", 1271 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", 1272 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", 1273 | "requires": { 1274 | "chownr": "^1.1.1", 1275 | "mkdirp-classic": "^0.5.2", 1276 | "pump": "^3.0.0", 1277 | "tar-stream": "^2.1.4" 1278 | } 1279 | }, 1280 | "tar-stream": { 1281 | "version": "2.2.0", 1282 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 1283 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 1284 | "requires": { 1285 | "bl": "^4.0.3", 1286 | "end-of-stream": "^1.4.1", 1287 | "fs-constants": "^1.0.0", 1288 | "inherits": "^2.0.3", 1289 | "readable-stream": "^3.1.1" 1290 | }, 1291 | "dependencies": { 1292 | "readable-stream": { 1293 | "version": "3.6.0", 1294 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 1295 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1296 | "requires": { 1297 | "inherits": "^2.0.3", 1298 | "string_decoder": "^1.1.1", 1299 | "util-deprecate": "^1.0.1" 1300 | } 1301 | } 1302 | } 1303 | }, 1304 | "through": { 1305 | "version": "2.3.8", 1306 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1307 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" 1308 | }, 1309 | "through2": { 1310 | "version": "0.4.2", 1311 | "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", 1312 | "integrity": "sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==", 1313 | "requires": { 1314 | "readable-stream": "~1.0.17", 1315 | "xtend": "~2.1.1" 1316 | } 1317 | }, 1318 | "tr46": { 1319 | "version": "0.0.3", 1320 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1321 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 1322 | }, 1323 | "tunnel-agent": { 1324 | "version": "0.6.0", 1325 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1326 | "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 1327 | "requires": { 1328 | "safe-buffer": "^5.0.1" 1329 | } 1330 | }, 1331 | "typedarray": { 1332 | "version": "0.0.6", 1333 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1334 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" 1335 | }, 1336 | "typedarray-pool": { 1337 | "version": "1.2.0", 1338 | "resolved": "https://registry.npmjs.org/typedarray-pool/-/typedarray-pool-1.2.0.tgz", 1339 | "integrity": "sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ==", 1340 | "requires": { 1341 | "bit-twiddle": "^1.0.0", 1342 | "dup": "^1.0.0" 1343 | } 1344 | }, 1345 | "uglify-js": { 1346 | "version": "2.8.29", 1347 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", 1348 | "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", 1349 | "requires": { 1350 | "source-map": "~0.5.1", 1351 | "uglify-to-browserify": "~1.0.0", 1352 | "yargs": "~3.10.0" 1353 | }, 1354 | "dependencies": { 1355 | "source-map": { 1356 | "version": "0.5.7", 1357 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1358 | "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" 1359 | } 1360 | } 1361 | }, 1362 | "uglify-to-browserify": { 1363 | "version": "1.0.2", 1364 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 1365 | "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", 1366 | "optional": true 1367 | }, 1368 | "underscore": { 1369 | "version": "1.13.4", 1370 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", 1371 | "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==" 1372 | }, 1373 | "uniq": { 1374 | "version": "1.0.1", 1375 | "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", 1376 | "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==" 1377 | }, 1378 | "util-deprecate": { 1379 | "version": "1.0.2", 1380 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1381 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1382 | }, 1383 | "webidl-conversions": { 1384 | "version": "3.0.1", 1385 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1386 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 1387 | }, 1388 | "whatwg-url": { 1389 | "version": "5.0.0", 1390 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1391 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 1392 | "requires": { 1393 | "tr46": "~0.0.3", 1394 | "webidl-conversions": "^3.0.0" 1395 | } 1396 | }, 1397 | "wide-align": { 1398 | "version": "1.1.5", 1399 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 1400 | "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", 1401 | "requires": { 1402 | "string-width": "^1.0.2 || 2 || 3 || 4" 1403 | } 1404 | }, 1405 | "window-size": { 1406 | "version": "0.1.0", 1407 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 1408 | "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==" 1409 | }, 1410 | "wordwrap": { 1411 | "version": "0.0.2", 1412 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 1413 | "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==" 1414 | }, 1415 | "wrappy": { 1416 | "version": "1.0.2", 1417 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1418 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1419 | }, 1420 | "xtend": { 1421 | "version": "2.1.2", 1422 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", 1423 | "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", 1424 | "requires": { 1425 | "object-keys": "~0.4.0" 1426 | } 1427 | }, 1428 | "yallist": { 1429 | "version": "4.0.0", 1430 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1431 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1432 | }, 1433 | "yargs": { 1434 | "version": "3.10.0", 1435 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", 1436 | "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", 1437 | "requires": { 1438 | "camelcase": "^1.0.2", 1439 | "cliui": "^2.1.0", 1440 | "decamelize": "^1.0.0", 1441 | "window-size": "0.1.0" 1442 | } 1443 | } 1444 | } 1445 | } 1446 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algo-test-harness", 3 | "version": "1.0.0", 4 | "description": "Test harness for several image similarity algorithms using the Pinecone vector DB", 5 | "main": "vectorize.js", 6 | "engines": { 7 | "node": "16.15.0" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "canvas": "^2.9.3", 13 | "node-fetch": "^2.6.6", 14 | "numjs": "^0.16.1", 15 | "underscore": "^1.13.4", 16 | "JSONStream": "^1.3.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /process-images.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const {createCanvas, loadImage} = require('canvas') 3 | 4 | function scaleImage(image, imageData, scale) { 5 | const newWidth = Math.floor(image.width * scale) 6 | const newHeight = Math.floor(image.height * scale) 7 | let canvas = createCanvas(newWidth, newHeight) 8 | 9 | let ctx = canvas.getContext("2d"); 10 | ctx.drawImage(image, 0, 0, imageData.width, imageData.height, 0, 0, newWidth, newHeight) 11 | 12 | return canvas 13 | } 14 | 15 | function cropImage(image, imageData, pct) { 16 | const newWidth = Math.floor(image.width * pct) 17 | const newHeight = Math.floor(image.height * pct) 18 | let canvas = createCanvas(image.width, image.height) 19 | let ctx = canvas.getContext("2d"); 20 | ctx.drawImage(image, 0, 0, imageData.width, imageData.height) 21 | 22 | const newImageData = ctx.getImageData(Math.floor((image.width - newWidth) / 2), Math.floor((image.height - newHeight) / 2), newWidth, newHeight); 23 | 24 | const newCanvas = createCanvas(newImageData.width, newImageData.height); 25 | const newCtx = newCanvas.getContext('2d'); 26 | newCtx.putImageData(newImageData, 0, 0); 27 | 28 | return newCanvas 29 | } 30 | 31 | function writeFile(path, mime, canvas) { 32 | console.log(`Writing: ${path}`) 33 | const buffer = canvas.toBuffer(mime) 34 | fs.writeFileSync(path, buffer) 35 | } 36 | 37 | function run(sourceFolder) { 38 | fs.readdir(sourceFolder, async (err, files) => { 39 | const promises = [] 40 | for (let file of files.filter(f => f.match(/.*jpg/))) { 41 | promises.push(loadImage(`${sourceFolder}/${file}`).then(async (image) => { 42 | const canvas = createCanvas(image.width, image.height) 43 | const ctx = canvas.getContext('2d') 44 | ctx.drawImage(image, 0, 0, image.width, image.height) 45 | const imageData = ctx.getImageData(0, 0, image.width, image.height) 46 | 47 | const cropped = cropImage(image, imageData, 0.90) 48 | writeFile(`${sourceFolder}/cropped/${file}`, 'image/jpeg', cropped) 49 | 50 | const grown = scaleImage(image, imageData, 2) 51 | writeFile(`${sourceFolder}/grown/${file}`, 'image/jpeg', grown) 52 | 53 | const shrunk = scaleImage(image, imageData, 0.5) 54 | writeFile(`${sourceFolder}/shrunk/${file}`, 'image/jpeg', shrunk) 55 | 56 | const pngFile = file.replace('jpg', 'png') 57 | writeFile(`${sourceFolder}/reformatted/${pngFile}`, 'image/png', canvas) 58 | 59 | }).catch(err => { 60 | console.log(err) 61 | return false 62 | }) 63 | ) 64 | } 65 | 66 | await Promise.all(promises) 67 | }); 68 | } 69 | 70 | run('./images') -------------------------------------------------------------------------------- /query-matching.js: -------------------------------------------------------------------------------- 1 | const {makePineconeClient} = require("./src/pinecone") 2 | const {loadVectorGroups, outputResultCSV} = require("./src/common"); 3 | 4 | const pineconeApiKey = process.env.PINECONE_API_KEY 5 | if (!pineconeApiKey) { 6 | throw new Error("Pinecone API key not found") 7 | } 8 | 9 | function pctTrue(arr) { 10 | if (arr.length === 0) { 11 | return 0 12 | } 13 | 14 | const correctCnt = arr.filter(b => b).length 15 | return (1.0 * correctCnt) / arr.length 16 | } 17 | 18 | function correctScore(sha, matches) { 19 | const sameSha = matches.filter(match => match.sha256 === sha) 20 | 21 | if (sameSha.length > 0) { 22 | return sameSha[0].score 23 | } else { 24 | return null 25 | } 26 | } 27 | 28 | function highestIncorrectScore(sha, matches) { 29 | for (let match of matches) { 30 | if (match.sha256 !== sha) { 31 | return match.score 32 | } 33 | } 34 | 35 | return null; 36 | } 37 | 38 | function outputMatchingPctCSV(image, topIsCorrect, correctIsPresent) { 39 | console.log(`${image},${pctTrue(topIsCorrect)},${pctTrue(correctIsPresent)}`) 40 | } 41 | 42 | async function getResults(name, pinecone, metric, vectorGroups) { 43 | const sameMatchingScore = []; 44 | const sameHighestNonMatching = []; 45 | const sameCorrectPresent = []; 46 | const sameTopIsCorrect = []; 47 | 48 | const croppedMatchingScore = []; 49 | const croppedHighestNonMatching = []; 50 | const croppedCorrectPresent = []; 51 | const croppedTopIsCorrect = []; 52 | 53 | const grownMatchingScore = []; 54 | const grownHighestNonMatching = []; 55 | const grownCorrectPresent = []; 56 | const grownTopIsCorrect = []; 57 | 58 | const shrunkMatchingScore = []; 59 | const shrunkHighestNonMatching = []; 60 | const shrunkCorrectPresent = []; 61 | const shrunkTopIsCorrect = []; 62 | 63 | const reformattedMatchingScore = []; 64 | const reformattedHighestNonMatching = []; 65 | const reformattedCorrectPresent = []; 66 | const reformattedTopIsCorrect = []; 67 | 68 | const query = async (vector, sha, matchingScore, highestNonMatchingScore, topIsCorrect, correctPresent) => { 69 | const results = await pinecone.query(vector, 10); 70 | 71 | const cScore = correctScore(sha, results) 72 | if (cScore) { 73 | matchingScore.push(cScore) 74 | correctPresent.push(true) 75 | } else { 76 | correctPresent.push(false) 77 | } 78 | 79 | const highestIncorrect = highestIncorrectScore(sha, results) 80 | if (highestIncorrect) { 81 | highestNonMatchingScore.push(highestIncorrect) 82 | } 83 | 84 | if (results.length > 0) { 85 | topIsCorrect.push(results[0].sha256 === sha) 86 | } 87 | } 88 | 89 | let numQueried = 0; 90 | const toQuery = Object.keys(vectorGroups).length 91 | for (const [sha, vectorGroup] of Object.entries(vectorGroups)) { 92 | await query(vectorGroup.same, sha, sameMatchingScore, sameHighestNonMatching, sameTopIsCorrect, sameCorrectPresent) 93 | await query(vectorGroup.cropped, sha, croppedMatchingScore, croppedHighestNonMatching, croppedTopIsCorrect, croppedCorrectPresent) 94 | await query(vectorGroup.grown, sha, grownMatchingScore, grownHighestNonMatching, grownTopIsCorrect, grownCorrectPresent) 95 | await query(vectorGroup.shrunk, sha, shrunkMatchingScore, shrunkHighestNonMatching, shrunkTopIsCorrect, shrunkCorrectPresent) 96 | await query(vectorGroup.reformatted, sha, reformattedMatchingScore, reformattedHighestNonMatching, reformattedTopIsCorrect, reformattedCorrectPresent) 97 | numQueried++ 98 | console.error(`${name}: Queried ${metric} for ${numQueried}/${toQuery}`) 99 | } 100 | 101 | sameMatchingScore.sort((a, b) => a - b) 102 | sameHighestNonMatching.sort((a, b) => a - b) 103 | croppedMatchingScore.sort((a, b) => a - b) 104 | croppedHighestNonMatching.sort((a, b) => a - b) 105 | grownMatchingScore.sort((a, b) => a - b) 106 | grownHighestNonMatching.sort((a, b) => a - b) 107 | shrunkMatchingScore.sort((a, b) => a - b) 108 | shrunkHighestNonMatching.sort((a, b) => a - b) 109 | reformattedMatchingScore.sort((a, b) => a - b) 110 | reformattedHighestNonMatching.sort((a, b) => a - b) 111 | 112 | console.log("Similarity Function,Distance Metric") 113 | console.log(`${name},${metric}`) 114 | console.log() 115 | 116 | console.log("Image,Top Correct, Correct Present") 117 | outputMatchingPctCSV("Same", sameTopIsCorrect, sameCorrectPresent) 118 | outputMatchingPctCSV("Cropped", croppedTopIsCorrect, croppedCorrectPresent) 119 | outputMatchingPctCSV("Grown", grownTopIsCorrect, grownCorrectPresent) 120 | outputMatchingPctCSV("Shrunk", shrunkTopIsCorrect, shrunkCorrectPresent) 121 | outputMatchingPctCSV("Reformatted", reformattedTopIsCorrect, reformattedCorrectPresent) 122 | console.log() 123 | 124 | console.log("Category,Average,Minimum,25th Percentile,50th Percentile,75th Percentile,90th Percentile,95th Percentile,99th Percentile,99.9th Percentile,Max") 125 | outputResultCSV("Same - Matching Score", sameMatchingScore) 126 | outputResultCSV("Same - Highest Non Matching", sameHighestNonMatching) 127 | outputResultCSV("Cropped - Matching Score", croppedMatchingScore) 128 | outputResultCSV("Cropped - Highest Non Matching", croppedHighestNonMatching) 129 | outputResultCSV("Grown - Matching Score", grownMatchingScore) 130 | outputResultCSV("Grown - Highest Non Matching", grownHighestNonMatching) 131 | outputResultCSV("Shrunk - Matching Score", shrunkMatchingScore) 132 | outputResultCSV("Shrunk - Highest Non Matching", shrunkHighestNonMatching) 133 | outputResultCSV("Reformatted - Matching Score", reformattedMatchingScore) 134 | outputResultCSV("Reformatted - Highest Non Matching", reformattedHighestNonMatching) 135 | } 136 | 137 | async function run(name, vectorFile, pineconeUrls) { 138 | const pinecones = {} 139 | for (let [metric, url] of Object.entries(pineconeUrls)) { 140 | pinecones[metric] = makePineconeClient(pineconeApiKey, url) 141 | } 142 | 143 | const vectorGroups = await loadVectorGroups(vectorFile) 144 | 145 | for (let [metric, pinecone] of Object.entries(pinecones)) { 146 | await getResults(name, pinecone, metric, vectorGroups) 147 | } 148 | } 149 | 150 | (async () => { 151 | const start = Date.now() 152 | const goldbergPineconeUrls = { 153 | Cosine: "https://goldberg-544-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 154 | DotProduct: "https://goldberg-544-dot-b335ecb.svc.us-west1-gcp.pinecone.io", 155 | Euclidean: "https://goldberg-544-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 156 | } 157 | await run("Goldberg", "Goldberg-vectors.json", goldbergPineconeUrls) 158 | 159 | const pHashPineconeUrls = { 160 | Cosine: "https://phash-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 161 | Euclidean: "https://phash-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 162 | } 163 | await run("pHash", "pHash-vectors-1660522856022.json", pHashPineconeUrls) 164 | 165 | const dctPineconeUrls = { 166 | Cosine: "https://dct-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 167 | Euclidean: "https://dct-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 168 | } 169 | await run("DCT", "DCT-vectors-1660528334395.json", dctPineconeUrls) 170 | 171 | const intensityPineconeUrls = { 172 | Cosine: "https://intensity-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 173 | Euclidean: "https://intensity-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 174 | } 175 | await run("Intensity", "Intensity-vectors-1660521980204.json", intensityPineconeUrls) 176 | 177 | console.error(`Finished in ${Date.now() - start}ms`) 178 | })() 179 | 180 | -------------------------------------------------------------------------------- /query-missing.js: -------------------------------------------------------------------------------- 1 | const {makePineconeClient} = require("./src/pinecone") 2 | const {loadVectorGroups, outputResultCSV} = require("./src/common"); 3 | 4 | const pineconeApiKey = process.env.PINECONE_API_KEY 5 | if (!pineconeApiKey) { 6 | throw new Error("Pinecone API key not found") 7 | } 8 | 9 | async function getResults(name, pinecone, metric, vectorGroups) { 10 | const highestScore = []; 11 | const allScores = []; 12 | 13 | let numQueried = 0; 14 | const toQuery = Object.keys(vectorGroups).length 15 | for (const vectorGroup of Object.values(vectorGroups)) { 16 | const results = await pinecone.query(vectorGroup.same, 10); 17 | if (results && results.length > 0) { 18 | highestScore.push(results[0].score) 19 | results.forEach(result => allScores.push(result.score)) 20 | } 21 | 22 | numQueried++ 23 | console.error(`Queried ${metric} for ${numQueried}/${toQuery}`) 24 | } 25 | 26 | highestScore.sort((a, b) => a - b) 27 | allScores.sort((a, b) => a - b) 28 | 29 | console.log("Similarity Function,Distance Metric") 30 | console.log(`${name},${metric}`) 31 | console.log() 32 | 33 | console.log("Category,Average,Minimum,25th Percentile,50th Percentile,75th Percentile,90th Percentile,95th Percentile,99th Percentile,99.9th Percentile,Max") 34 | outputResultCSV("Missing - Highest Score", highestScore) 35 | outputResultCSV("Missing - All Scores", allScores) 36 | } 37 | 38 | async function run(name, vectorFile, pineconeUrls) { 39 | const pinecones = {} 40 | for (let [metric, url] of Object.entries(pineconeUrls)) { 41 | pinecones[metric] = makePineconeClient(pineconeApiKey, url) 42 | } 43 | 44 | const vectorGroups = await loadVectorGroups(vectorFile) 45 | 46 | for (let [metric, pinecone] of Object.entries(pinecones)) { 47 | await getResults(name, pinecone, metric, vectorGroups) 48 | } 49 | } 50 | 51 | (async () => { 52 | const start = Date.now() 53 | const goldbergPineconeUrls = { 54 | Cosine: "https://goldberg-544-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 55 | DotProduct: "https://goldberg-544-dot-b335ecb.svc.us-west1-gcp.pinecone.io", 56 | Euclidean: "https://goldberg-544-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 57 | } 58 | await run("Goldberg", "missing-Goldberg-vectors-1660564797912.json", goldbergPineconeUrls) 59 | 60 | const pHashPineconeUrls = { 61 | Cosine: "https://phash-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 62 | Euclidean: "https://phash-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 63 | } 64 | await run("pHash", "missing-pHash-vectors-1660564896508.json", pHashPineconeUrls) 65 | 66 | const dctPineconeUrls = { 67 | Cosine: "https://dct-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 68 | Euclidean: "https://dct-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 69 | } 70 | await run("DCT", "missing-DCT-vectors-1660564864437.json", dctPineconeUrls) 71 | 72 | const intensityPineconeUrls = { 73 | Cosine: "https://intensity-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 74 | Euclidean: "https://intensity-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 75 | } 76 | await run("Intensity", "missing-Intensity-vectors-1660564928478.json", intensityPineconeUrls) 77 | 78 | console.error(`Finished in ${Date.now() - start}ms`) 79 | })() 80 | 81 | -------------------------------------------------------------------------------- /sample-images/T000000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000000.jpg -------------------------------------------------------------------------------- /sample-images/T000001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000001.jpg -------------------------------------------------------------------------------- /sample-images/T000002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000002.jpg -------------------------------------------------------------------------------- /sample-images/T000003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000003.jpg -------------------------------------------------------------------------------- /sample-images/T000004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000004.jpg -------------------------------------------------------------------------------- /sample-images/T000005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000005.jpg -------------------------------------------------------------------------------- /sample-images/T000006.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000006.jpg -------------------------------------------------------------------------------- /sample-images/T000007.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000007.jpg -------------------------------------------------------------------------------- /sample-images/T000008.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000008.jpg -------------------------------------------------------------------------------- /sample-images/T000009.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000009.jpg -------------------------------------------------------------------------------- /sample-images/T000010.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000010.jpg -------------------------------------------------------------------------------- /sample-images/T000011.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000011.jpg -------------------------------------------------------------------------------- /sample-images/T000012.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000012.jpg -------------------------------------------------------------------------------- /sample-images/T000013.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000013.jpg -------------------------------------------------------------------------------- /sample-images/T000014.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000014.jpg -------------------------------------------------------------------------------- /sample-images/T000015.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000015.jpg -------------------------------------------------------------------------------- /sample-images/T000016.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000016.jpg -------------------------------------------------------------------------------- /sample-images/T000017.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000017.jpg -------------------------------------------------------------------------------- /sample-images/T000018.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000018.jpg -------------------------------------------------------------------------------- /sample-images/T000019.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000019.jpg -------------------------------------------------------------------------------- /sample-images/T000020.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000020.jpg -------------------------------------------------------------------------------- /sample-images/T000021.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000021.jpg -------------------------------------------------------------------------------- /sample-images/T000022.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000022.jpg -------------------------------------------------------------------------------- /sample-images/T000023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000023.jpg -------------------------------------------------------------------------------- /sample-images/T000024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000024.jpg -------------------------------------------------------------------------------- /sample-images/T000025.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000025.jpg -------------------------------------------------------------------------------- /sample-images/T000026.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000026.jpg -------------------------------------------------------------------------------- /sample-images/T000027.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000027.jpg -------------------------------------------------------------------------------- /sample-images/T000028.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000028.jpg -------------------------------------------------------------------------------- /sample-images/T000029.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000029.jpg -------------------------------------------------------------------------------- /sample-images/T000030.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000030.jpg -------------------------------------------------------------------------------- /sample-images/T000031.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000031.jpg -------------------------------------------------------------------------------- /sample-images/T000032.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000032.jpg -------------------------------------------------------------------------------- /sample-images/T000033.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000033.jpg -------------------------------------------------------------------------------- /sample-images/T000034.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000034.jpg -------------------------------------------------------------------------------- /sample-images/T000035.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000035.jpg -------------------------------------------------------------------------------- /sample-images/T000036.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000036.jpg -------------------------------------------------------------------------------- /sample-images/T000037.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000037.jpg -------------------------------------------------------------------------------- /sample-images/T000038.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000038.jpg -------------------------------------------------------------------------------- /sample-images/T000039.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000039.jpg -------------------------------------------------------------------------------- /sample-images/T000040.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000040.jpg -------------------------------------------------------------------------------- /sample-images/T000041.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000041.jpg -------------------------------------------------------------------------------- /sample-images/T000042.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000042.jpg -------------------------------------------------------------------------------- /sample-images/T000043.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000043.jpg -------------------------------------------------------------------------------- /sample-images/T000044.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000044.jpg -------------------------------------------------------------------------------- /sample-images/T000045.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000045.jpg -------------------------------------------------------------------------------- /sample-images/T000046.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000046.jpg -------------------------------------------------------------------------------- /sample-images/T000047.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000047.jpg -------------------------------------------------------------------------------- /sample-images/T000048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000048.jpg -------------------------------------------------------------------------------- /sample-images/T000049.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000049.jpg -------------------------------------------------------------------------------- /sample-images/T000050.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000050.jpg -------------------------------------------------------------------------------- /sample-images/T000051.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000051.jpg -------------------------------------------------------------------------------- /sample-images/T000052.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000052.jpg -------------------------------------------------------------------------------- /sample-images/T000053.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000053.jpg -------------------------------------------------------------------------------- /sample-images/T000054.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000054.jpg -------------------------------------------------------------------------------- /sample-images/T000055.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000055.jpg -------------------------------------------------------------------------------- /sample-images/T000056.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000056.jpg -------------------------------------------------------------------------------- /sample-images/T000057.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000057.jpg -------------------------------------------------------------------------------- /sample-images/T000058.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000058.jpg -------------------------------------------------------------------------------- /sample-images/T000059.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000059.jpg -------------------------------------------------------------------------------- /sample-images/T000060.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000060.jpg -------------------------------------------------------------------------------- /sample-images/T000061.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000061.jpg -------------------------------------------------------------------------------- /sample-images/T000062.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000062.jpg -------------------------------------------------------------------------------- /sample-images/T000063.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000063.jpg -------------------------------------------------------------------------------- /sample-images/T000064.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000064.jpg -------------------------------------------------------------------------------- /sample-images/T000065.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000065.jpg -------------------------------------------------------------------------------- /sample-images/T000066.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000066.jpg -------------------------------------------------------------------------------- /sample-images/T000067.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000067.jpg -------------------------------------------------------------------------------- /sample-images/T000068.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000068.jpg -------------------------------------------------------------------------------- /sample-images/T000069.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000069.jpg -------------------------------------------------------------------------------- /sample-images/T000070.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000070.jpg -------------------------------------------------------------------------------- /sample-images/T000071.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000071.jpg -------------------------------------------------------------------------------- /sample-images/T000072.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000072.jpg -------------------------------------------------------------------------------- /sample-images/T000073.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000073.jpg -------------------------------------------------------------------------------- /sample-images/T000074.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000074.jpg -------------------------------------------------------------------------------- /sample-images/T000075.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000075.jpg -------------------------------------------------------------------------------- /sample-images/T000076.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000076.jpg -------------------------------------------------------------------------------- /sample-images/T000077.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000077.jpg -------------------------------------------------------------------------------- /sample-images/T000078.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000078.jpg -------------------------------------------------------------------------------- /sample-images/T000079.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000079.jpg -------------------------------------------------------------------------------- /sample-images/T000080.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000080.jpg -------------------------------------------------------------------------------- /sample-images/T000081.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000081.jpg -------------------------------------------------------------------------------- /sample-images/T000082.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000082.jpg -------------------------------------------------------------------------------- /sample-images/T000083.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000083.jpg -------------------------------------------------------------------------------- /sample-images/T000084.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000084.jpg -------------------------------------------------------------------------------- /sample-images/T000085.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000085.jpg -------------------------------------------------------------------------------- /sample-images/T000086.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000086.jpg -------------------------------------------------------------------------------- /sample-images/T000087.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000087.jpg -------------------------------------------------------------------------------- /sample-images/T000088.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000088.jpg -------------------------------------------------------------------------------- /sample-images/T000089.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000089.jpg -------------------------------------------------------------------------------- /sample-images/T000090.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000090.jpg -------------------------------------------------------------------------------- /sample-images/T000091.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000091.jpg -------------------------------------------------------------------------------- /sample-images/T000092.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000092.jpg -------------------------------------------------------------------------------- /sample-images/T000093.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000093.jpg -------------------------------------------------------------------------------- /sample-images/T000094.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000094.jpg -------------------------------------------------------------------------------- /sample-images/T000095.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000095.jpg -------------------------------------------------------------------------------- /sample-images/T000096.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000096.jpg -------------------------------------------------------------------------------- /sample-images/T000097.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000097.jpg -------------------------------------------------------------------------------- /sample-images/T000098.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000098.jpg -------------------------------------------------------------------------------- /sample-images/T000099.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-text-org/image-algo-testing/f54010e4d56bce95484d0b08ae4254d38fb4a649/sample-images/T000099.jpg -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | const {createCanvas, loadImage} = require("canvas"); 2 | const fs = require("fs") 3 | const JSONStream = require("JSONStream"); 4 | 5 | function shrinkImage(image, imageData, edgeLength) { 6 | let canvas = createCanvas(edgeLength, edgeLength) 7 | 8 | let ctx = canvas.getContext("2d"); 9 | 10 | ctx.drawImage(image, 0, 0, imageData.width, imageData.height, 0, 0, edgeLength, edgeLength) 11 | return ctx.getImageData(0, 0, edgeLength, edgeLength); 12 | } 13 | 14 | function toGreyscale(imageData) { 15 | let rgba = new Uint8Array(imageData.data.buffer); 16 | let greyscale = new Uint8Array(rgba.length / 4); 17 | for (let i = 0, j = 0; i < rgba.length; i += 4, j++) { 18 | let intensity = (rgba[i] + rgba[i + 1] + rgba[i + 2]) * (rgba[i + 3] / 255.0); 19 | greyscale[j] = Math.round((intensity / 765) * 255); 20 | } 21 | 22 | return greyscale; 23 | } 24 | 25 | async function loadVectorGroups(file) { 26 | return new Promise((resolve, reject) => { 27 | const jsonStream = JSONStream.parse([{emitKey: true}]); 28 | const readableStream = fs.createReadStream(file, 'utf8').pipe(jsonStream); 29 | 30 | readableStream.on('error', function (error) { 31 | reject(error) 32 | }) 33 | 34 | const result = {} 35 | jsonStream.on('data', function(data) { 36 | result[data.key] = data.value 37 | }); 38 | 39 | jsonStream.on('close', () => resolve(result)) 40 | }) 41 | } 42 | 43 | function average(arr) { 44 | const sum = arr.reduce((a, b) => a + b, 0) 45 | return (1.0 * sum) / arr.length 46 | } 47 | 48 | function getQuantile(arr, quantile) { 49 | const idx = Math.floor(arr.length * quantile) 50 | return arr[idx] 51 | } 52 | 53 | function outputResultCSV(name, arr) { 54 | console.log( 55 | `${name},` + 56 | `${average(arr)},` + 57 | `${arr[0]},` + 58 | `${getQuantile(arr, 0.25)},` + 59 | `${getQuantile(arr, 0.50)},` + 60 | `${getQuantile(arr, 0.75)},` + 61 | `${getQuantile(arr, 0.90)},` + 62 | `${getQuantile(arr, 0.95)},` + 63 | `${getQuantile(arr, 0.99)},` + 64 | `${getQuantile(arr, 0.999)},` + 65 | `${arr[arr.length - 1]}` 66 | ) 67 | } 68 | 69 | async function loadImageFile(path) { 70 | const image = await loadImage(path) 71 | const canvas = createCanvas(image.width, image.height) 72 | const ctx = canvas.getContext('2d') 73 | ctx.drawImage(image, 0, 0, image.width, image.height) 74 | const imageData = ctx.getImageData(0, 0, image.width, image.height) 75 | 76 | return { 77 | image: image, 78 | imageData: imageData 79 | } 80 | } 81 | 82 | exports.shrinkImage = shrinkImage; 83 | exports.toGreyscale = toGreyscale; 84 | exports.loadVectorGroups = loadVectorGroups; 85 | exports.average = average; 86 | exports.getQuantile = getQuantile; 87 | exports.outputResultCSV = outputResultCSV; 88 | exports.loadImageFile = loadImageFile; -------------------------------------------------------------------------------- /src/dct/dct.js: -------------------------------------------------------------------------------- 1 | const {shrinkImage, toGreyscale} = require("../common") 2 | /** 3 | * This is copied from https://github.com/Sherryer/dct2, which is distributed under an MIT license 4 | * 5 | * It has been modified to memoize calls to Math.cos() 6 | */ 7 | 8 | const Hash = {}; 9 | const Cosines = {}; 10 | 11 | const cosine = (first, second, len) => { 12 | if (!Cosines[len]) { 13 | Cosines[len] = {}; 14 | } 15 | let lenCosines = Cosines[len] 16 | 17 | if (!lenCosines[first]) { 18 | Cosines[len][first] = {}; 19 | } 20 | let lenFirstCosines = Cosines[len][first] 21 | 22 | if (!lenFirstCosines[second]) { 23 | Cosines[len][first][second] = Math.cos((2 * first + 1) * Math.PI * second / 2 / len) 24 | } 25 | 26 | return lenFirstCosines[second]; 27 | } 28 | 29 | const getCoff = (index, length) => { 30 | if (!Hash[length]) { 31 | let coff = []; 32 | coff[0] = 1 / Math.sqrt(length); 33 | for (let i = 1; i < length; i++) { 34 | coff[i] = Math.sqrt(2) / Math.sqrt(length); 35 | } 36 | Hash[length] = coff; 37 | } 38 | return Hash[length][index]; 39 | }; 40 | 41 | const DCT = (signal) => { 42 | const length = signal.length; 43 | let tmp = Array(length * length).fill(0); 44 | let res = Array(length).fill('').map(() => []); 45 | for (let i = 0; i < length; i++) { 46 | for (let j = 0; j < length; j++) { 47 | for (let x = 0; x < length; x++) { 48 | tmp[i * length + j] += getCoff(j, length) * signal[i][x] * cosine(x, j, length); 49 | } 50 | } 51 | } 52 | for (let i = 0; i < length; i++) { 53 | for (let j = 0; j < length; j++) { 54 | for (let x = 0; x < length; x++) { 55 | res[i][j] = (res[i][j] || 0) + getCoff(i, length) * tmp[x * length + j] * cosine(x, i, length) 56 | } 57 | } 58 | } 59 | return res 60 | }; 61 | 62 | // End copied code 63 | 64 | const diagonalSnake = (matrix, rows, cols) => { 65 | const result = new Array(rows * cols); 66 | let resultIdx = 0; 67 | for (let line = 1; line <= (rows + cols - 1); line++) { 68 | let start_col = Math.max(0, line - rows); 69 | let count = Math.min(line, (cols - start_col), rows); 70 | for (let j = 0; j < count; j++) { 71 | result[resultIdx] = matrix[Math.min(rows, line) - j - 1][start_col + j]; 72 | resultIdx++; 73 | } 74 | } 75 | 76 | return result 77 | } 78 | 79 | function getTopLeft(pixels, edgeLength) { 80 | let res = Array(edgeLength).fill('').map(() => []); 81 | 82 | for (let row = 0; row < edgeLength; row++) { 83 | for (let col = 0; col < edgeLength; col++) { 84 | res[row][col] = pixels[row][col]; 85 | } 86 | } 87 | 88 | return res; 89 | } 90 | 91 | function toMatrix(arr, rows, cols) { 92 | if (arr.length !== rows * cols) { 93 | throw new Error("Array length must equal requested rows * columns") 94 | } 95 | 96 | const matrix = []; 97 | for (let i = 0; i < rows; i++) { 98 | matrix[i] = []; 99 | for (let j = 0; j < cols; j++) { 100 | matrix[i][j] = arr[(i * cols) + j]; 101 | } 102 | } 103 | 104 | return matrix; 105 | } 106 | 107 | async function dct1024Image(image, imageData) { 108 | let shrunk = shrinkImage(image, imageData, 64); 109 | let greyed = toGreyscale(shrunk); 110 | let matrix = toMatrix(greyed, 64, 64) 111 | let dct = DCT(matrix); 112 | let trimmed = getTopLeft(dct, 32); 113 | return diagonalSnake(trimmed, 32, 32) 114 | } 115 | 116 | exports.dct1024Image = dct1024Image; -------------------------------------------------------------------------------- /src/image-signature-js/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 William MacDonald 4 | Updates copyright (c) 2022 Hannah Kolbeck 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /src/image-signature-js/array_util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('underscore') 4 | 5 | function flatMap(arr, fun) { 6 | return Array.prototype.concat.apply([], arr.map(fun)) 7 | } 8 | 9 | const flatten = arr => [].concat.apply([], arr) 10 | 11 | function sliding(arr, groupSize, increment) { 12 | let result = [] 13 | for (let i = 0; i < arr.length; i += increment) { 14 | let group = arr.slice(i, i + groupSize) 15 | result.push(group) 16 | } 17 | return result 18 | } 19 | 20 | // produces a collection containing cumulative results of applying the operator going left to right 21 | function scanLeft(initial, arr, operator) { 22 | const newArray = [initial] 23 | _.reduce(arr, (memo, el) => { 24 | const result = operator(memo, el) 25 | newArray.push(result) 26 | return result 27 | }, initial) 28 | return newArray 29 | } 30 | 31 | 32 | exports.flatMap = flatMap 33 | exports.flatten = flatten 34 | exports.sliding = sliding 35 | exports.scanLeft = scanLeft 36 | -------------------------------------------------------------------------------- /src/image-signature-js/image_signature.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = require('underscore') 4 | const nj = require('numjs') 5 | const njUtil = require('./nj_util') 6 | const arrayUtil = require('./array_util') 7 | 8 | function goldberg(image, imageData) { 9 | const img = nj.array(imageData.data) 10 | const rgb = img.reshape(imageData.height, imageData.width, 4) 11 | 12 | let flattened; 13 | if (rgb.selection.data.length === imageData.height * imageData.width * 4) { 14 | flattened = rgb.selection.data 15 | } else { 16 | flattened = rgb.selection.data[0] 17 | } 18 | 19 | const gray = nj.array(grayscale(flattened)) 20 | const reshaped = gray.reshape(imageData.height, imageData.width) 21 | 22 | const cropped = autoCrop(reshaped, 10, 90) 23 | 24 | // put 10x10 grid on image, compute average values for 81 grid points 25 | const gridAverages = computeGridAverages(cropped, 10, 10) 26 | const flattenedAverages = gridAverages.flatten().tolist() 27 | 28 | // neighbor (exclusive) grid point averages for each grid point average 29 | const gridNeighbors = _.map(flattenedAverages, (avg, idx) => njUtil.getNeighbors(gridAverages, ...mCoords(idx, ...gridAverages.shape), -1, 1, false)) 30 | 31 | // differential between grid points and their neighbors 32 | const differentialGroups = _.map(_.zip(flattenedAverages, gridNeighbors), ([avg, neighbors]) => _.map(neighbors.flatten().tolist(), neighbor => neighbor - avg)) 33 | 34 | const positive = nj.array(_.filter(arrayUtil.flatten(differentialGroups), differential => differential > 2)) 35 | const negative = nj.array(_.filter(arrayUtil.flatten(differentialGroups), differential => differential < -2)) 36 | 37 | // get boundaries between lighter and much lighter so both are as popular 38 | const positiveCutoff = njUtil.percentile(positive, 50) 39 | // get boundaries between dark and much darker 40 | const negativeCutoff = njUtil.percentile(negative, 50) 41 | 42 | // function to turn gray values to 43 | const normalizeWithCutoffs = _.partial(normalize, 2, positiveCutoff, negativeCutoff) 44 | 45 | return _.map(differentialGroups, differentials => _.map(differentials, normalizeWithCutoffs)).flat() 46 | } 47 | 48 | // rgbaData is a a Uint8ClampedArray from ImageData.data 49 | function grayscale(rgbData) { 50 | let rgbArrays = arrayUtil.sliding(rgbData, 3, 4) 51 | let grays = rgbArrays.map(average) 52 | return grays.map(Math.round) 53 | } 54 | 55 | function autoCrop(gray, lowerPercentile, upperPercentile) { 56 | // row-wise differences 57 | const rw = njUtil.cumsum(njUtil.sum(nj.abs(njUtil.diff(gray, undefined, 1)), 1)) 58 | // column-wise differences 59 | const cw = njUtil.cumsum(njUtil.sum(nj.abs(njUtil.diff(gray, undefined, 0)), 0)) 60 | 61 | const rowTotal = rw.get(-1) 62 | const colTotal = cw.get(-1) 63 | 64 | const upperRowLimit = njUtil.searchsorted(rw, rowTotal * upperPercentile / 100) 65 | const lowerRowLimit = njUtil.searchsorted(rw, rowTotal * lowerPercentile / 100) 66 | 67 | const upperColLimit = njUtil.searchsorted(cw, colTotal * upperPercentile / 100) 68 | const lowerColLimit = njUtil.searchsorted(cw, colTotal * lowerPercentile / 100) 69 | 70 | return gray.slice([lowerRowLimit, upperRowLimit + 1], [lowerColLimit, upperColLimit + 1]) 71 | } 72 | 73 | 74 | function computeGridAverages(imageArray, numBlocksHigh, numBlocksWide) { 75 | const squareHeight = imageArray.shape[0] / numBlocksHigh 76 | const squareWidth = imageArray.shape[1] / numBlocksWide 77 | 78 | // represents width of square in Goldberg paper 79 | const P = Math.max(2, Math.floor(0.5 + Math.min(imageArray.shape[0], imageArray.shape[1]) / 20)) 80 | // used to calculate the upper and lower offsets of x and y for the edges of the squares 81 | const upperOffset = Math.ceil((P - 1) / 2) 82 | const lowerOffset = -Math.floor((P - 1) / 2) 83 | 84 | // divide height into 10 segments and make range from the points 85 | const gridYs = _.map(_.range(squareHeight, 86 | imageArray.shape[0] - squareHeight / 2 /* to avoid floating point error and make sure it ends at the 9th number */, 87 | squareHeight), Math.floor) 88 | const gridXs = _.map(_.range(squareWidth, imageArray.shape[1] - squareWidth / 2, squareWidth), Math.floor) 89 | 90 | const gridCoords = cartesianProductOf(gridXs, gridYs) 91 | 92 | const squares = _.map(gridCoords, ([x, y]) => njUtil.getNeighbors(imageArray, y, x, lowerOffset, upperOffset, true)) 93 | const squareAverages = _.map(squares, s => s.mean()) 94 | const array = nj.array(squareAverages) 95 | // create 3D matrix - first two dimensions represent the square position in the image 96 | // and the third dimension contains the neighbor values (0 means no neighbor) 97 | return array.reshape(numBlocksHigh - 1, numBlocksWide - 1) 98 | } 99 | 100 | function normalize(equalCutoff, positiveCutoff, negativeCutoff, value) { 101 | if (value < -equalCutoff) { 102 | if (value < negativeCutoff) { 103 | // much darker 104 | return -2 105 | } else { 106 | // darker 107 | return -1 108 | } 109 | } else if (value > equalCutoff) { 110 | if (value > positiveCutoff) { 111 | // much lighter 112 | return 2 113 | } else { 114 | // lighter 115 | return 1 116 | } 117 | } 118 | // same 119 | else { 120 | return 0 121 | } 122 | } 123 | 124 | exports.goldberg = goldberg 125 | 126 | // http://stackoverflow.com/a/23823717 127 | function average(arr) { 128 | return arr.reduce(function (sum, a, i, ar) { 129 | sum += a; 130 | return i === ar.length - 1 ? (ar.length === 0 ? 0 : sum / ar.length) : sum 131 | }, 0) 132 | } 133 | 134 | // http://stackoverflow.com/a/12628791 135 | function cartesianProductOf() { 136 | return _.reduce(arguments, function (a, b) { 137 | return _.flatten(_.map(a, function (x) { 138 | return _.map(b, function (y) { 139 | return x.concat([y]) 140 | }) 141 | }), true) 142 | }, [[]]) 143 | } 144 | 145 | // returns coords from index into flat array 146 | // rs: num rows, cs: num columns 147 | function mCoords(idx, rs, cs) { 148 | return [Math.floor(idx / rs), idx % cs] 149 | } 150 | -------------------------------------------------------------------------------- /src/image-signature-js/nj_util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nj = require('numjs') 4 | const _ = require('underscore') 5 | 6 | const arrayUtil = require('./array_util') 7 | 8 | // https://github.com/numpy/numpy/blob/v1.10.1/numpy/lib/function_base.py#L1116-L1175 9 | function diff(a, n, axis) { 10 | // default to 11 | n = n === undefined ? 1 : n 12 | if (n === 0) { 13 | return a 14 | } 15 | if (n < 0) { 16 | throw new Error('order must be non-negative but got ' + n) 17 | } 18 | 19 | let nd = a.shape.length 20 | let slice1 = _.map(_.range(nd), el => [null]) 21 | let slice2 = _.map(_.range(nd), el => [null]) 22 | // default to last axis 23 | axis = axis === undefined ? nd - 1 : axis 24 | slice1[axis] = 1 25 | slice2[axis] = [null, -1] 26 | if (n > 1) { 27 | return diff(a.slice(...slice1).subtract(a.slice(...slice2), n - 1, axis)) 28 | } else { 29 | return a.slice(...slice1).subtract(a.slice(...slice2)) 30 | } 31 | } 32 | 33 | function sum(a, axis) { 34 | if (axis === undefined) { 35 | return a.flatten().sum() 36 | } 37 | 38 | const nd = a.shape.length 39 | const axisLength = a.shape[axis] 40 | const sliceShape = a.shape.slice() 41 | sliceShape.splice(axis, 1) 42 | 43 | if (sliceShape.length === 0) { 44 | // get cumulative sum of array (splice to discard initial 0) 45 | return nj.array(arrayUtil.scanLeft(0, a.tolist(), (prev, curr) => prev + curr).slice(1)) 46 | } else { 47 | let slice = _.map(_.range(nd), el => [null, null, null]) 48 | 49 | let sumSlice = nj.zeros(sliceShape) 50 | // LOOP INVARIANT: sumSlice is the cum sum up to i on the axis 51 | for (let i = 0; i < axisLength; i++) { 52 | // slice array at i on the given axis 53 | slice[axis] = [i, i + 1] 54 | const row = a.slice(...slice).reshape(...sliceShape) 55 | sumSlice = sumSlice.add(row) 56 | } 57 | return sumSlice 58 | } 59 | } 60 | 61 | 62 | // TODO test for array rank > 2 63 | function cumsum(a, axis) { 64 | if (axis === undefined) { 65 | return cumsum(a.flatten(), 0) 66 | } 67 | 68 | const nd = a.shape.length 69 | const axisLength = a.shape[axis] 70 | const sliceShape = a.shape.slice() 71 | sliceShape.splice(axis, 1) 72 | 73 | if (sliceShape.length === 0) { 74 | // get cumulative sum of array (splice to discard initial 0) 75 | return nj.array(arrayUtil.scanLeft(0, a.tolist(), (prev, curr) => prev + curr).slice(1)) 76 | } else { 77 | let slice = _.map(_.range(nd), el => [null, null, null]) 78 | 79 | const result = [] 80 | let sumSlice = nj.zeros(sliceShape) 81 | // LOOP INVARIANT: result is the cum sum up to i on the axis 82 | for (let i = 0; i < axisLength; i++) { 83 | // slice array at i on the given axis 84 | slice[axis] = [i, i + 1] 85 | const row = a.slice(...slice).reshape(...sliceShape) 86 | sumSlice = sumSlice.add(row) 87 | // add the sum to the result array 88 | result.push(sumSlice.tolist()) 89 | } 90 | const axes = _.range(0, a.shape.length) 91 | axes[0] = axis 92 | axes[axis] = 0 93 | return nj.array(result).transpose(axes) 94 | } 95 | } 96 | 97 | // http://stackoverflow.com/questions/22697936/binary-search-in-javascript 98 | function searchsorted(a, v) { 99 | let m = 0; 100 | let n = a.shape[0] - 1 101 | while (m <= n) { 102 | const k = (n + m) >> 1 103 | const cmp = v - a.get(k) 104 | if (cmp > 0) { 105 | m = k + 1 106 | } else if (cmp < 0) { 107 | n = k - 1 108 | } else { 109 | return k 110 | } 111 | } 112 | return m 113 | } 114 | 115 | function vectorize(a, fun) { 116 | return nj.array(_.map(a.flatten().tolist(), fun)).reshape(...a.shape) 117 | } 118 | 119 | // lowerOffset is negative 120 | function getNeighbors(a, r, c, lowerOffset, upperOffset, includeSelf) { 121 | const clipRow = _.partial(clip, 0, a.shape[0] - 1) 122 | const clipColumn = _.partial(clip, 0, a.shape[1] - 1) 123 | 124 | if (includeSelf) { 125 | const rowSlice = [clipRow(r + lowerOffset), clipRow(r + upperOffset) + 1] 126 | const columnSlice = [clipColumn(c + lowerOffset), clipColumn(c + upperOffset) + 1] 127 | const neighbors = a.slice(rowSlice, columnSlice).flatten().tolist() 128 | return nj.array(neighbors) 129 | } else { 130 | // get slices for upper, left, right, and lower partitions without SELF 131 | /* [ UPPER ] 132 | * [LEFT] SELF [RIGHT] 133 | * [ LOWER ] 134 | */ 135 | const upperRowSlice = [clipRow(r + lowerOffset), r] 136 | const upperColumnSlice = [clipColumn(c + lowerOffset), clipColumn(c + upperOffset) + 1] 137 | 138 | const leftRowSlice = [r, r + 1] 139 | const leftColumnSlice = [clipColumn(c + lowerOffset), c] 140 | 141 | const rightRowSlice = [r, r + 1] 142 | const rightColumnSlice = [c + 1, clipColumn(c + upperOffset) + 1] 143 | 144 | const lowerRowSlice = [r + 1, clipRow(r + upperOffset) + 1] 145 | const lowerColumnSlice = [clipColumn(c + lowerOffset), clipColumn(c + upperOffset) + 1] 146 | 147 | const rowSlices = [upperRowSlice, leftRowSlice, rightRowSlice, lowerRowSlice] 148 | const columnSlices = [upperColumnSlice, leftColumnSlice, rightColumnSlice, lowerColumnSlice] 149 | 150 | const neighborGroups = arrayUtil.flatMap(_.zip(rowSlices, columnSlices), ([rowSlice, columnSlice]) => a.slice(rowSlice, columnSlice).flatten().tolist()) 151 | const neighbors = arrayUtil.flatten(neighborGroups) 152 | return nj.array(neighbors) 153 | } 154 | } 155 | 156 | function sort(a) { 157 | const arr = a.flatten().tolist() 158 | arr.sort() 159 | return nj.array(arr) 160 | } 161 | 162 | function percentile(a, q) { 163 | const sorted = sort(a.flatten()) 164 | const rank = q / 100 * (sorted.shape[0] + 1) - 1 165 | if (Number.isInteger(rank)) { 166 | if (sorted.get(rank) === undefined) { 167 | return null 168 | } else { 169 | return sorted.get(rank) 170 | } 171 | } else { 172 | const IR = Math.floor(rank) 173 | const FR = rank - IR 174 | const lower = sorted.get(IR) 175 | const upper = sorted.get(IR + 1) 176 | if (lower === undefined && upper === undefined) { 177 | return null 178 | } else if (lower === undefined) { 179 | return upper 180 | } else if (upper === undefined) { 181 | return lower 182 | } else { 183 | const interpolated = FR * (upper - lower) + lower 184 | return interpolated 185 | } 186 | } 187 | } 188 | 189 | const l2Norm = arr => Math.sqrt(vectorize(arr, x => Math.pow(x, 2)).sum()) 190 | 191 | const distance = (a1, a2) => l2Norm(a1.subtract(a2)) / (l2Norm(a1) + l2Norm(a2)) 192 | 193 | exports.diff = diff 194 | exports.sum = sum 195 | exports.cumsum = cumsum 196 | exports.searchsorted = searchsorted 197 | exports.vectorize = vectorize 198 | exports.getNeighbors = getNeighbors 199 | exports.sort = sort 200 | exports.percentile = percentile 201 | exports.l2Norm = l2Norm 202 | exports.distance = distance 203 | 204 | function clip(start, end, value) { 205 | if (value < start) { 206 | return start 207 | } else if (value > end) { 208 | return end 209 | } else { 210 | return value 211 | } 212 | } 213 | 214 | -------------------------------------------------------------------------------- /src/intensity/intensity.js: -------------------------------------------------------------------------------- 1 | const {shrinkImage, toGreyscale} = require("../common") 2 | 3 | function intensity1024(image, imageData) { 4 | const shrunk = shrinkImage(image, imageData, 32) 5 | return Array.from(toGreyscale(shrunk)) 6 | } 7 | 8 | exports.intensity1024 = intensity1024 -------------------------------------------------------------------------------- /src/phash/phash.js: -------------------------------------------------------------------------------- 1 | const {shrinkImage, toGreyscale} = require("../common") 2 | 3 | function average(arr) { 4 | const sum = arr.reduce((a, b) => a + b, 0) 5 | return sum / arr.length 6 | } 7 | 8 | function reduceToBinary(arr, avg) { 9 | return arr.map(val => val > avg ? 1 : 0) 10 | } 11 | 12 | function pHash1024(image, imageData) { 13 | const shrunk = shrinkImage(image, imageData, 32) 14 | const greyScale = toGreyscale(shrunk) 15 | const avg = average(greyScale) 16 | return Array.from(reduceToBinary(greyScale, avg)) 17 | } 18 | 19 | exports.pHash1024 = pHash1024 -------------------------------------------------------------------------------- /src/pinecone.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | 3 | function makeClient(apiKey, url) { 4 | return { 5 | upsert: upserter(apiKey, `${url}/vectors/upsert`, "alt"), 6 | query: queryer(apiKey, `${url}/query`, "alt") 7 | }; 8 | } 9 | 10 | function upserter(apiKey, url, namespace) { 11 | return async (sha256, vector) => { 12 | const payload = { 13 | vectors: [ 14 | { 15 | id: sha256, 16 | values: vector, 17 | }, 18 | ], 19 | namespace: namespace, 20 | }; 21 | 22 | const delay = () => new Promise(resolve => setTimeout(resolve, 500)); 23 | let attempt = 1 24 | while (true) { 25 | const upserted = await upsertOnce(apiKey, url, payload) 26 | if (upserted) { 27 | return true 28 | } 29 | 30 | console.error(`Upsert failed on attempt ${attempt}`) 31 | attempt++ 32 | await delay() 33 | } 34 | }; 35 | } 36 | 37 | async function upsertOnce(apiKey, url, payload) { 38 | const resp = await fetch( 39 | url, 40 | { 41 | method: "POST", 42 | headers: { 43 | "Api-Key": apiKey, 44 | "Content-Type": "application/json", 45 | }, 46 | body: JSON.stringify(payload), 47 | } 48 | ).catch(() => { 49 | return null; 50 | }); 51 | 52 | if (resp) { 53 | if (resp.ok) { 54 | return true 55 | } else { 56 | console.error(`HTTP ${resp.status} ${resp.statusText}: ${await resp.text()}`) 57 | return false 58 | } 59 | } else { 60 | return false 61 | } 62 | } 63 | 64 | function queryer(apiKey, url, namespace) { 65 | return async (vector, maxResults) => { 66 | const payload = { 67 | namespace: namespace, 68 | topK: maxResults, 69 | vector: vector, 70 | }; 71 | 72 | const resp = await fetch( 73 | url, 74 | { 75 | method: "POST", 76 | headers: { 77 | "Api-Key": apiKey, 78 | "Content-Type": "application/json", 79 | }, 80 | body: JSON.stringify(payload), 81 | } 82 | ).catch((err) => { 83 | console.error(err); 84 | return null; 85 | }); 86 | 87 | if (!resp) { 88 | throw new Error("Fetch errored."); 89 | } else if (!resp.ok) { 90 | throw new Error( 91 | `Got non-ok response from Pinecone /query endpoint: ${ 92 | resp.status 93 | }: Body: '${await resp.text()}'` 94 | ); 95 | } 96 | 97 | const body = await resp.json(); 98 | 99 | if (!body.matches) { 100 | throw new Error(`Malformed response body: ${JSON.stringify(body)}`); 101 | } 102 | 103 | return body.matches.map((match) => { 104 | return { 105 | sha256: match.id, 106 | score: match.score, 107 | }; 108 | }); 109 | }; 110 | } 111 | 112 | exports.makePineconeClient = makeClient; 113 | -------------------------------------------------------------------------------- /src/sha256.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto") 2 | 3 | function sha256(imageData) { 4 | return crypto 5 | .createHash("sha256") 6 | .update(Buffer.from(imageData.data.buffer)) 7 | .digest("hex"); 8 | } 9 | 10 | exports.sha256 = sha256 -------------------------------------------------------------------------------- /upsert.js: -------------------------------------------------------------------------------- 1 | const {loadVectorGroups} = require("./src/common"); 2 | const {makePineconeClient} = require("./src/pinecone"); 3 | 4 | const pineconeApiKey = process.env.PINECONE_API_KEY 5 | if (!pineconeApiKey) { 6 | throw new Error("Pinecone API key not found") 7 | } 8 | 9 | async function run(name, vectorFile, pineconeUrls) { 10 | const pinecones = {} 11 | for (let [metric, url] of Object.entries(pineconeUrls)) { 12 | pinecones[metric] = makePineconeClient(pineconeApiKey, url) 13 | } 14 | 15 | const vectorGroups = await loadVectorGroups(vectorFile) 16 | 17 | for (const [sha, vectorGroup] of Object.entries(vectorGroups)) { 18 | for (let pinecone of Object.values(pinecones)) { 19 | await pinecone.upsert(sha, vectorGroup.same) 20 | } 21 | console.log(`${name}: Upserted: ${vectorGroup.file}`) 22 | } 23 | } 24 | 25 | (async () => { 26 | const start = Date.now() 27 | const goldbergPineconeUrls = { 28 | Cosine: "https://goldberg-544-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 29 | DotProduct: "https://goldberg-544-dot-b335ecb.svc.us-west1-gcp.pinecone.io", 30 | Euclidean: "https://goldberg-544-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 31 | } 32 | await run("Goldberg", "Goldberg-vectors.json", goldbergPineconeUrls) 33 | 34 | const pHashPineconeUrls = { 35 | Cosine: "https://phash-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 36 | Euclidean: "https://phash-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 37 | } 38 | await run("pHash", "pHash-vectors-1660522856022.json", pHashPineconeUrls) 39 | 40 | const dctPineconeUrls = { 41 | Cosine: "https://dct-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 42 | Euclidean: "https://dct-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 43 | } 44 | await run("DCT", "DCT-vectors-1660528334395.json", dctPineconeUrls) 45 | 46 | const intensityPineconeUrls = { 47 | Cosine: "https://intensity-1024-cosine-b335ecb.svc.us-west1-gcp.pinecone.io", 48 | Euclidean: "https://intensity-1024-euclid-b335ecb.svc.us-west1-gcp.pinecone.io" 49 | } 50 | await run("Intensity", "Intensity-vectors-1660521980204.json", intensityPineconeUrls) 51 | 52 | console.error(`Finished in ${Date.now() - start}ms`) 53 | })() 54 | -------------------------------------------------------------------------------- /vectorize-missing.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | 3 | const JSONStream = require("JSONStream") 4 | 5 | const {sha256} = require("./src/sha256") 6 | const {pHash1024} = require("./src/phash/phash") 7 | const {dct1024Image} = require("./src/dct/dct") 8 | const {intensity1024} = require("./src/intensity/intensity") 9 | const {goldberg} = require("./src/image-signature-js/image_signature") 10 | const {loadImageFile} = require("./src/common"); 11 | 12 | function run(name, sourceFolder, vectorizer) { 13 | fs.readdir(sourceFolder, async (err, files) => { 14 | const vectorGroups = {} 15 | 16 | for (let file of files.filter(f => f.match(/.*jpg/))) { 17 | console.error(`${name}: Processing ${file}`) 18 | const {image, imageData} = await loadImageFile(`${sourceFolder}/${file}`) 19 | 20 | const vectorGroup = { 21 | file: file, 22 | same: await vectorizer(image, imageData), 23 | } 24 | 25 | const sha = sha256(imageData) 26 | vectorGroups[sha] = vectorGroup; 27 | } 28 | 29 | const jsonStream = JSONStream.stringifyObject(); 30 | const outputStream = fs.createWriteStream(`missing-${name}-vectors-${Date.now()}.json`); 31 | jsonStream.pipe(outputStream) 32 | for (const [sha, vectorGroup] of Object.entries(vectorGroups)) { 33 | jsonStream.write([sha, vectorGroup]) 34 | } 35 | jsonStream.end(); 36 | 37 | outputStream.on( 38 | "finish", 39 | function handleFinish() { 40 | console.log("Done"); 41 | } 42 | ); 43 | }); 44 | } 45 | 46 | run("Goldberg","./missing-images", goldberg) 47 | run("DCT", "./missing-images", dct1024Image) 48 | run("Intensity", "./missing-images", intensity1024) 49 | run("pHash", "./missing-images", pHash1024) 50 | -------------------------------------------------------------------------------- /vectorize.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | 3 | const {createCanvas, loadImage} = require('canvas') 4 | const JSONStream = require("JSONStream") 5 | 6 | const {sha256} = require("./src/sha256") 7 | const {pHash1024} = require("./src/phash/phash") 8 | const {dct1024Image} = require("./src/dct/dct") 9 | const {intensity1024} = require("./src/intensity/intensity") 10 | const {goldberg} = require("./src/image-signature-js/image_signature") 11 | const {outputResultCSV, loadImageFile} = require("./src/common"); 12 | 13 | function run(name, sourceFolder, vectorizer) { 14 | const start = Date.now() 15 | const vectorCalcTimes = [] 16 | 17 | fs.readdir(sourceFolder, async (err, files) => { 18 | const vectorGroups = {} 19 | 20 | for (let file of files.filter(f => f.match(/.*jpg/))) { 21 | console.error(`${name}: Processing ${file}`) 22 | const {image, imageData} = await loadImageFile(`${sourceFolder}/${file}`) 23 | const {image: shrunkImage, imageData: shrunkImageData} = await loadImageFile(`${sourceFolder}/shrunk/${file}`) 24 | const {image: grownImage, imageData: grownImageData} = await loadImageFile(`${sourceFolder}/grown/${file}`) 25 | const {image: croppedImage, imageData: croppedImageData} = await loadImageFile(`${sourceFolder}/cropped/${file}`) 26 | const {image: reformattedImage, imageData: reformattedImageData} = await loadImageFile(`${sourceFolder}/reformatted/${file.replace('jpg', 'png')}`) 27 | 28 | const vectorCalcStart = Date.now() 29 | const vectorGroup = { 30 | file: file, 31 | same: await vectorizer(image, imageData), 32 | cropped: await vectorizer(croppedImage, croppedImageData), 33 | grown: await vectorizer(grownImage, grownImageData), 34 | shrunk: await vectorizer(shrunkImage, shrunkImageData), 35 | reformatted: await vectorizer(reformattedImage, reformattedImageData) 36 | } 37 | vectorCalcTimes.push(Date.now() - vectorCalcStart) 38 | 39 | const sha = sha256(imageData) 40 | vectorGroups[sha] = vectorGroup; 41 | } 42 | 43 | const jsonStream = JSONStream.stringifyObject(); 44 | const outputStream = fs.createWriteStream(`${name}-vectors-${Date.now()}.json`); 45 | jsonStream.pipe(outputStream) 46 | for (const [sha, vectorGroup] of Object.entries(vectorGroups)) { 47 | jsonStream.write([sha, vectorGroup]) 48 | } 49 | jsonStream.end(); 50 | 51 | outputStream.on( 52 | "finish", 53 | function handleFinish() { 54 | console.log("Done"); 55 | } 56 | ); 57 | 58 | 59 | vectorCalcTimes.sort((a, b) => a - b) 60 | console.error(`Finished in ${Date.now() - start}ms`) 61 | console.log("Timing,Average,Minimum,25th Percentile,50th Percentile,75th Percentile,90th Percentile,95th Percentile,99th Percentile,99.9th Percentile,Max") 62 | outputResultCSV(name, vectorCalcTimes) 63 | }); 64 | } 65 | 66 | // run("Goldberg","./images", goldberg) 67 | run("DCT", "./sample-images", dct1024Image) 68 | // run("Intensity", "./images", intensity1024) 69 | // run("pHash", "./images", pHash1024) 70 | --------------------------------------------------------------------------------