@paulhectork/aiiinotate 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +206 -0
  3. package/cli/export.js +118 -0
  4. package/cli/import.js +111 -0
  5. package/cli/index.js +45 -0
  6. package/cli/migrate.js +131 -0
  7. package/cli/serve.js +29 -0
  8. package/cli/utils/fastifyClient.js +86 -0
  9. package/cli/utils/io.js +225 -0
  10. package/cli/utils/mongoClient.js +21 -0
  11. package/cli/utils/progressbar.js +86 -0
  12. package/cli/xywhToInt.js +99 -0
  13. package/config/.env.template +48 -0
  14. package/docker/Dockerfile +39 -0
  15. package/docker/README.md +5 -0
  16. package/docker/docker-compose.yaml +49 -0
  17. package/docker/docker.sh +43 -0
  18. package/docker/docker_aiiinotate_import.sh +62 -0
  19. package/docs/api.md +381 -0
  20. package/docs/cli.md +132 -0
  21. package/docs/dev_documentation/dev_architecture.md +88 -0
  22. package/docs/dev_documentation/dev_db.md +58 -0
  23. package/docs/dev_documentation/dev_iiif_compatibility.md +43 -0
  24. package/docs/dev_documentation/dev_notes_quirks_and_troubleshooting.md +143 -0
  25. package/docs/docker.md +98 -0
  26. package/docs/includes/report_benchmark_aiiinotate_2026-05-28-02:50:48_7steps.png +0 -0
  27. package/docs/scalability.md +34 -0
  28. package/docs/specifications/0_w3c_open_annotations.md +332 -0
  29. package/docs/specifications/1_w3c_web_annotations.md +577 -0
  30. package/docs/specifications/2_iiif_apis.md +428 -0
  31. package/docs/specifications/3_iiif_annotations.md +103 -0
  32. package/docs/specifications/4_search_api.md +135 -0
  33. package/docs/specifications/5_sas.md +119 -0
  34. package/docs/specifications/6_mirador.md +119 -0
  35. package/docs/specifications/7_aikon.md +137 -0
  36. package/docs/specifications/include/presentation_2.0.webp +0 -0
  37. package/docs/specifications/include/presentation_2.0_white.png +0 -0
  38. package/docs/specifications/include/presentation_3.0.png +0 -0
  39. package/docs/specifications/include/presentation_3.0_resize.png +0 -0
  40. package/eslint.config.js +30 -0
  41. package/migrations/baseConfig.js +57 -0
  42. package/migrations/manageIndex.js +55 -0
  43. package/migrations/migrate-mongo-config-main.js +8 -0
  44. package/migrations/migrate-mongo-config-test.js +8 -0
  45. package/migrations/migrationScripts/20250825185706-collections.js +48 -0
  46. package/migrations/migrationScripts/20250826194832-annotations2-schema.js +42 -0
  47. package/migrations/migrationScripts/20250904080710-annotations2-indexes.js +69 -0
  48. package/migrations/migrationScripts/20251002141951-manifests2-schema.js +43 -0
  49. package/migrations/migrationScripts/20251006212110-manifests2-indexes.js +35 -0
  50. package/migrations/migrationTemplate.js +25 -0
  51. package/package.json +82 -0
  52. package/scripts/get_version.py +12 -0
  53. package/scripts/run.sh +36 -0
  54. package/scripts/setup_mongodb.sh +70 -0
  55. package/scripts/setup_node.sh +15 -0
  56. package/scripts/update_version.py +30 -0
  57. package/scripts/utils.sh +65 -0
  58. package/src/app.js +116 -0
  59. package/src/constants.js +73 -0
  60. package/src/data/annotations/annotations2.js +681 -0
  61. package/src/data/annotations/annotations3.js +28 -0
  62. package/src/data/annotations/routes.js +335 -0
  63. package/src/data/annotations/routes.test.js +271 -0
  64. package/src/data/collectionAbstract.js +283 -0
  65. package/src/data/index.js +29 -0
  66. package/src/data/manifests/manifests2.js +378 -0
  67. package/src/data/manifests/manifests2.test.js +53 -0
  68. package/src/data/manifests/manifests3.js +23 -0
  69. package/src/data/manifests/routes.js +122 -0
  70. package/src/data/manifests/routes.test.js +70 -0
  71. package/src/data/routes.js +181 -0
  72. package/src/data/routes.test.js +166 -0
  73. package/src/db/index.js +50 -0
  74. package/src/fixtures/annotations.js +41 -0
  75. package/src/fixtures/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
  76. package/src/fixtures/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
  77. package/src/fixtures/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
  78. package/src/fixtures/data/annotations2Invalid.jsonld +39 -0
  79. package/src/fixtures/data/annotations2SvgValid.jsonld +81 -0
  80. package/src/fixtures/data/annotations2Valid.jsonld +39 -0
  81. package/src/fixtures/data/bnf_invalid_manifest.json +2806 -0
  82. package/src/fixtures/data/bnf_valid_manifest.json +2817 -0
  83. package/src/fixtures/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
  84. package/src/fixtures/generate.js +181 -0
  85. package/src/fixtures/index.js +69 -0
  86. package/src/fixtures/manifests.js +14 -0
  87. package/src/fixtures/utils.js +37 -0
  88. package/src/schemas/index.js +100 -0
  89. package/src/schemas/schemasBase.js +19 -0
  90. package/src/schemas/schemasPresentation2.js +410 -0
  91. package/src/schemas/schemasPresentation3.js +33 -0
  92. package/src/schemas/schemasResolver.js +71 -0
  93. package/src/schemas/schemasRoutes.js +318 -0
  94. package/src/server.js +25 -0
  95. package/src/types.js +97 -0
  96. package/src/utils/iiif2Utils.js +332 -0
  97. package/src/utils/iiif2Utils.test.js +146 -0
  98. package/src/utils/iiif3Utils.js +0 -0
  99. package/src/utils/iiifUtils.js +18 -0
  100. package/src/utils/logger.js +119 -0
  101. package/src/utils/routeUtils.js +137 -0
  102. package/src/utils/svg.js +417 -0
  103. package/src/utils/testUtils.js +289 -0
  104. package/src/utils/utils.js +403 -0
@@ -0,0 +1,86 @@
1
+ import build from "#src/app.js";
2
+
3
+ /** @typedef {import("#types").FastifyInstanceType} FastifyInstanceType */
4
+ /** @typedef {import("#types").FastifyReplyType} FastifyReplyType */
5
+ /** @typedef {import("#types").InsertResponseType} InsertResponseType */
6
+
7
+ /**
8
+ * a client to interact with the fastify app.
9
+ *
10
+ * @example
11
+ * import FastifyClient from "#cli/utils/fastifyClient.js";
12
+ * const fastify = new FastifyClient();
13
+ * // load the fastify instance asynchronously
14
+ * await fastify.build();
15
+ * // run operations
16
+ * await fastify.importAnnotationList(...);
17
+ */
18
+ class FastifyClient {
19
+ constructor () { }
20
+
21
+ // NOTE: instanciating this.fastify is asynchronous and a constructor must be synchronous,
22
+ // so we use a builder to set this.fastify, following this pattern: https://stackoverflow.com/a/43433773
23
+ async build() {
24
+ /** @type {FastifyInstanceType} */
25
+ this.fastify = await build("default");
26
+ // TODO find way to actually completely disable logging because it makes the CLI UI uglyyy !
27
+ // log only fastify errors
28
+ this.fastify.log.level = "error";
29
+ }
30
+
31
+ async stop() {
32
+ await this.fastify.close();
33
+ this.fastify.log.info("fastify instance successfully closed.");
34
+ }
35
+
36
+ /**
37
+ * @param {string} route
38
+ * @returns {Promise<FastifyReplyType>}
39
+ */
40
+ injectGet(route) {
41
+ return this.fastify.inject({
42
+ method: "GET",
43
+ url: route,
44
+ })
45
+ }
46
+
47
+ /**
48
+ * @param {string} route
49
+ * @param {object} payload
50
+ * @returns {Promise<FastifyReplyType>}
51
+ */
52
+ injectPost(route, payload) {
53
+ return this.fastify.inject({
54
+ method: "POST",
55
+ url: route,
56
+ payload: payload
57
+ })
58
+ }
59
+
60
+ async importAnnotationPage(annotationPage) {
61
+ // TODO
62
+ }
63
+
64
+ /**
65
+ * function to interact with all annotation createMany + manifest create routes.
66
+ * @type {(iiifVersion: 2|3, datatype: "manifest"|"annotation") => (data: object) => [Number, Promise<object>] }
67
+ */
68
+ importData(iiifVersion, datatype) {
69
+ if (![ "2","3" ].includes(`${iiifVersion}`)) {
70
+ throw new Error(`fastifyClient.importData: "iiifVersion" must by 2 or 3, got "${iiifVersion}"`);
71
+ }
72
+ if (![ "manifest","annotation" ].includes(datatype)) {
73
+ throw new Error(`fastifyClient.importData: "datatype" must by "manifest" or "annotation", got "${datatype}"`);
74
+ }
75
+
76
+ const routeSuffix = datatype==="manifest" ? "create" : "createMany";
77
+ const route = `/${datatype}s/${iiifVersion}/${routeSuffix}`;
78
+
79
+ return async (data) => {
80
+ const r = await this.injectPost(route, data);
81
+ return [ r.statusCode, r.json() ];
82
+ }
83
+ }
84
+ }
85
+
86
+ export default FastifyClient;
@@ -0,0 +1,225 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+
4
+ import ProgressBar from "#cli/utils/progressbar.js";
5
+
6
+ /** @typedef {import("fs").WriteStream} WriteStreamType */
7
+
8
+ const cwd = process.cwd(); // directory the script is run from
9
+ const getCwd = () => process.cwd();
10
+
11
+ /**
12
+ * convert a filepath to absolute if it is relative.
13
+ * @param {string} f
14
+ * @returns {string}
15
+ */
16
+ const toAbsPath = (f) => path.isAbsolute(f) ? f : path.join(cwd, f);
17
+
18
+ /**
19
+ * @returns {[string, boolean]} - [<absolute file path>, <success?>]
20
+ */
21
+ const fileOk = (f) => {
22
+ f = toAbsPath(f);
23
+ try {
24
+ fs.accessSync(f, fs.constants.R_OK); // will throw if there's a problem opening.
25
+ return [ f, fs.lstatSync(f).isFile() ];
26
+ } catch (e) {
27
+ return [ f, false ]
28
+ }
29
+ }
30
+
31
+ /** @returns {[PathLike, boolean]} */
32
+ const dirOk = (d) => {
33
+ d = toAbsPath(d);
34
+ try {
35
+ fs.accessSync(d, fs.constants.R_OK); // will throw if there's an error
36
+ return [ d, fs.lstatSync(d).isDirectory() ]
37
+ } catch (e) {
38
+ return [ d, false ]
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @param {string} f
44
+ * @return {string?}
45
+ */
46
+ const fileRead = (f) => {
47
+ f = toAbsPath(f);
48
+ try {
49
+ return fs.readFileSync(f, { encoding: "utf8" })
50
+ } catch (err) {
51
+ console.error(`io.fileRead: could not read file: ${f}`);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * take an input array of filepaths. convert the paths to absolute, and check that the files exist
57
+ * the cli exits if any of the files don't exist
58
+ * @param {string[]} fileArr
59
+ * @returns { string[] } array of absolute filepaths
60
+ */
61
+ function fileArrayValidate (fileArr) {
62
+ // array of [<absolute filepath>, <success opening file?>]
63
+ fileArr = fileArr.map(fileOk);
64
+ const successCount = fileArr.filter(x => x[1]).length;
65
+ if (successCount!==fileArr.length) {
66
+ console.error(
67
+ `io.fileArrayValidate: ${fileArr.length - successCount} files could not be accessed. exiting...`,
68
+ fileArr.filter(x => !x[1]).map(x => x[0])
69
+ );
70
+ process.exit(1);
71
+ }
72
+ return fileArr.map(x => x[0]);
73
+ }
74
+
75
+ /**
76
+ * `file` is a path to a file containing paths to other files (1 file per line).
77
+ * parse the file and return an array of absolute paths to files.
78
+ * @param {str} file
79
+ * @returns {Promise<string[]>}
80
+ */
81
+ async function parseImportInputFile(file) {
82
+ // ensure input file exists
83
+ const [ fileAbs, ok ] = fileOk(file);
84
+ if (!ok) {
85
+ console.error(`could not read import file: ${file}. exiting...`);
86
+ process.exit(1);
87
+ }
88
+
89
+ // read `file` split it by lines, remove empty lines
90
+ const fileArr =
91
+ fileRead(fileAbs)
92
+ .split("\n")
93
+ .filter(l => !l.match(/^\s*$/g));
94
+ return [ ...new Set(fileArrayValidate(fileArr)) ];
95
+ }
96
+
97
+ const fileWrite = (f, data) => {
98
+ try {
99
+ fs.writeFileSync(f, data, "utf-8");
100
+ } catch (e) {
101
+ throw new Error(`Error writing to file: ${f} because of error: ${e}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * NOTE: this will NOT work on collections and huge objects. use streamWriteJson to write from Mongo Cursors.
107
+ * @param {string|import("fs").PathLike} f
108
+ * @param {Array|object} data
109
+ * @returns {void}
110
+ */
111
+ const fileWriteJson = (f, data) => {
112
+ data = JSON.stringify(data)
113
+ fileWrite(f, data);
114
+ }
115
+
116
+ /**
117
+ * wrapper for writer.write(data) that respects writer backpressure
118
+ *
119
+ * we wrap the .write() in a Promise that checks on the rturn of `writer.write`
120
+ * and drains the stream if necessary: if `writer.write()` returns
121
+ * false, the write buffer is full and must be drained by the OS
122
+ * before continuing.
123
+ *
124
+ * this avoids:
125
+ * - writes that are silently dropped or reordered
126
+ * - corrupt/truncated JSON with no error thrown
127
+ *
128
+ * @type {(writer: WriteStreamType) => (data: string) => Promise<void> }
129
+ */
130
+ const writeOrWait = (writer) =>
131
+ (data) =>
132
+ new Promise((resolve, reject) => {
133
+ const ok = writer.write(data, (error) => {
134
+ if (error) return reject(error);
135
+ });
136
+ if (ok) {
137
+ resolve(); // buffer has room, keep going
138
+ } else {
139
+ writer.once("drain", resolve); // buffer full, wait for drain
140
+ }
141
+ });
142
+
143
+ /**
144
+ * WriteStream.write is actually asyncrhonous => for the last line,
145
+ * we need to use `writer.end` to ensure everything has been written to file.
146
+ *
147
+ * @returns {Promise<void>}
148
+ */
149
+ const endStream = (writer, data) =>
150
+ new Promise((resolve, reject) => {
151
+ writer.end(data, (error) => {
152
+ if (error) return reject(error);
153
+ resolve();
154
+ });
155
+ });
156
+
157
+ /**
158
+ * given a file path `fp` and a FindCursor `cursor`,
159
+ * write all documents in `cursor` to a file.
160
+ *
161
+ * if `totalCount` is defined, print a progress bar as well.
162
+ * else, just print the document number and update at each iteration.
163
+ *
164
+ * necessary to use a string when `cursor` stores a lot of documents
165
+ * (instead of a simple file-write):
166
+ * - cursor.toArray() uses TONS of memory and is slow
167
+ * - JSON.stringify(), used to write JSONs to file, has a maximum
168
+ * size for arrays and will crash if stringifying huge arrays.
169
+ *
170
+ * to check that the output is a valid JSON, use:
171
+ * ```bash
172
+ * python3 -mjson.tool path/to/json > /dev/null && echo "ok" || echo "err"
173
+ * ````
174
+ *
175
+ * @param {string|PathLike} fp
176
+ * @param {MongoFindCursorType} cursor
177
+ * @param {number?} totalCount
178
+ * @returns {Promise}
179
+ */
180
+ const writeCursorToJson = async (fp, cursor, totalCount) => {
181
+ const writer = fs.createWriteStream(fp);
182
+ const onceWriter = writeOrWait(writer);
183
+
184
+ let pb;
185
+ if (totalCount) {
186
+ pb = new ProgressBar({ desc: "writing documents to file", total: totalCount });
187
+ } else {
188
+ console.log("");
189
+ }
190
+
191
+ // we are writing an array => manually write the opening "[".
192
+ await onceWriter("[");
193
+ let i = 0
194
+ for await (const doc of cursor) {
195
+ i += 1;
196
+ // if previous items were written to file, write a "," separator.
197
+ if (i!=1) await onceWriter(", ");
198
+ if (pb) {
199
+ pb.update(i);
200
+ } else {
201
+ process.stdout.clearLine(0);
202
+ process.stdout.cursorTo(0);
203
+ process.stdout.write(`writing document #${i} to file`);
204
+ }
205
+ await onceWriter(JSON.stringify(doc));
206
+ }
207
+ // close the array with a "]" and end the stream.
208
+ await endStream(writer, "]");
209
+
210
+ if (!pb && i>0) console.log("");
211
+ return totalCount;
212
+ }
213
+
214
+
215
+ export {
216
+ fileRead,
217
+ fileOk,
218
+ dirOk,
219
+ fileWrite,
220
+ fileWriteJson,
221
+ getCwd,
222
+ fileArrayValidate,
223
+ parseImportInputFile,
224
+ writeCursorToJson
225
+ }
@@ -0,0 +1,21 @@
1
+ import { MongoClient } from "mongodb";
2
+
3
+ import { MONGODB_CONNSTRING, MONGODB_DB } from "#constants";
4
+
5
+ /**
6
+ * load a mongo client and connect it to the database. exists if there's an error
7
+ * @returns {{ client: import("mongodb").MongoClient, db: import("mongodb").Db }}
8
+ */
9
+ function loadMongoClient() {
10
+ try {
11
+ const
12
+ client = new MongoClient(MONGODB_CONNSTRING),
13
+ db = client.db(MONGODB_DB);
14
+ return { client, db };
15
+ } catch (err) {
16
+ console.error(`mongoClient: could not connect to DB because of error ${err.message}`);
17
+ process.exit(1);
18
+ }
19
+ }
20
+
21
+ export default loadMongoClient;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * a progress bar
3
+ * @example
4
+ * const total = 100;
5
+ * const ms = 10;
6
+ * const pb = new ProgressBar({ desc: "Progressbar example", total: total });
7
+ * const sleep = (pb, i) => {
8
+ * pb.update(i);
9
+ * return new Promise((res, rej) => setTimeout(res, ms));
10
+ * };
11
+ * for (let i=0; i<=total; i++) {
12
+ * await sleep(pb, i);
13
+ * }
14
+ */
15
+ class ProgressBar {
16
+ constructor(options = {
17
+ desc: "Progress",
18
+ total: 100
19
+ }) {
20
+ this.desc = options.desc
21
+ this.total = options.total
22
+ this.current = 0;
23
+ this.startTime = Date.now();
24
+ }
25
+
26
+ update(current) {
27
+ this.current = Math.min(current, this.total);
28
+ this.render();
29
+ }
30
+
31
+ render() {
32
+ const terminalWidth = process.stdout.columns || 80;
33
+
34
+ const percent = this.total === 0 ? 100 : (this.current / this.total) * 100;
35
+ const percentStr = percent.toFixed(1) + "%";
36
+
37
+ // calculate elapsed and estimated time
38
+ const elapsedTime = Date.now() - this.startTime;
39
+ const elapsedSec = Math.floor(elapsedTime / 1000);
40
+ const rate = this.current / (elapsedTime / 1000);
41
+ const remainingTime = rate > 0 ? Math.floor((this.total - this.current) / rate) : 0;
42
+
43
+ const timeStr = `${this.formatTime(elapsedSec)}/${this.formatTime(remainingTime)}`;
44
+ const countStr = `${this.current}/${this.total}`;
45
+
46
+ // calculate available space for bar
47
+ const prefix = `${this.desc} [`;
48
+ const suffix = `] ${percentStr} ${countStr} ${timeStr}`;
49
+ const availableWidth = terminalWidth - prefix.length - suffix.length;
50
+
51
+ if (availableWidth < 10) {
52
+ process.stdout.write(`\r${this.desc}: ${percentStr}`);
53
+ return;
54
+ }
55
+
56
+ // build progress bar
57
+ const filledWidth = Math.floor((this.current / this.total) * availableWidth);
58
+ const emptyWidth = availableWidth - filledWidth;
59
+ const bar = "█".repeat(filledWidth) + "░".repeat(emptyWidth);
60
+
61
+ // build and display complete line
62
+ const line = prefix + bar + suffix;
63
+ process.stdout.write("\r" + line);
64
+
65
+ // new line when complete
66
+ if (this.current === this.total) {
67
+ process.stdout.write("\n");
68
+ }
69
+ }
70
+
71
+ formatTime(seconds) {
72
+ const h = Math.floor(seconds / 3600);
73
+ const m = Math.floor((seconds % 3600) / 60);
74
+ const s = seconds % 60;
75
+
76
+ if (h > 0) {
77
+ return `${h}h${m}m`;
78
+ } else if (m > 0) {
79
+ return `${m}m${s}s`;
80
+ } else {
81
+ return `${s}s`;
82
+ }
83
+ }
84
+ }
85
+
86
+ export default ProgressBar
@@ -0,0 +1,99 @@
1
+ import { Command, Option, Argument } from "commander";
2
+
3
+ import loadMongoClient from "#cli/utils/mongoClient.js";
4
+ import ProgressBar from "#cli/utils/progressbar.js";
5
+ import { maybeToArray, xywhToInt } from "#src/utils/utils.js";
6
+
7
+ /** @typedef {import("mongodb").Db} Db */
8
+
9
+ /**
10
+ * bash command to get faulty annotations from a docker aiiinotate instance:
11
+ * ```
12
+ * docker exec docker-mongo-1 mongosh vhs_aiiinotate --eval \
13
+ * "JSON.stringify(db.annotations2.find({ 'on.xywh': { \$type: 'double' } }, { '_id': 0 }).toArray(), null, 2);" \
14
+ * > mogno_export.json
15
+ * ```
16
+ *
17
+ * bash command to import annotations exported above in a local mongo instance:
18
+ * ```
19
+ * mongoimport \
20
+ * --host localhost \
21
+ * --db aiiinotate \
22
+ * --collection annotations2 \
23
+ * --file ./mogno_export.json \
24
+ * --jsonArray
25
+ * ```
26
+ */
27
+
28
+ /**
29
+ *
30
+ * @param {Db} db - the mongo database
31
+ * @param {object} annotation - the annotation to update
32
+ * @param {boolean} dryRun - if `false`, don't update the annotation in database
33
+ */
34
+ async function updateAnnotation(db, annotation, dryRun) {
35
+ let [ annotationTargetArray, converted ] = maybeToArray(annotation.on, true);
36
+
37
+ annotationTargetArray = annotationTargetArray.map((target) => {
38
+ target.xywh = xywhToInt(target.xywh);
39
+ console.log(target.xywh);
40
+ return target;
41
+ });
42
+ annotation.on = converted
43
+ ? annotationTargetArray[0]
44
+ : annotationTargetArray;
45
+
46
+ // console.log("post: ", annotation.on);
47
+ // we need to do 1 update / document, since the @id filter can select 1 document at a time.
48
+ if (!dryRun) {
49
+ await db.collection("annotations2").updateOne(
50
+ { "@id": annotation["@id"] },
51
+ { $set: annotation },
52
+ { upsert: false }
53
+ );
54
+ }
55
+ return
56
+ }
57
+
58
+ // NOTE: annotations have been exported from vhs and imported in local aiiinotate instance.
59
+ async function action(options) {
60
+ const dryRun = options.dryRun || false;
61
+
62
+ const summary = { total: 0, ok: 0, error: 0 };
63
+
64
+ const { client, db } = loadMongoClient();
65
+ const filter = { "on.xywh": { $type: "double" } };
66
+ const toUpdateCount = await db.collection("annotations2").countDocuments(filter);
67
+ const annotationsCursor = db.collection("annotations2").find(filter);
68
+ summary.total = toUpdateCount;
69
+
70
+ const pb = new ProgressBar({ desc: "updating annotations", total: toUpdateCount });
71
+ let i = 0;
72
+ while (await annotationsCursor.hasNext()) {
73
+ i += 1;
74
+ pb.update(i);
75
+ const annotation = await annotationsCursor.next();
76
+ try {
77
+ await updateAnnotation(db, annotation, dryRun);
78
+ summary.ok += 1;
79
+ } catch (e) {
80
+ console.error(e);
81
+ summary.error += 1
82
+ }
83
+ }
84
+
85
+ console.log(`converting annotation.on.xywh to int (dry run=${dryRun}). results:`, summary);
86
+ await client.close()
87
+ process.exit(0);
88
+ }
89
+
90
+ function makeXywhToIntCommand() {
91
+ const dryRunOpt = new Option("-d, --dry-run", "dry run (don't update data)");
92
+
93
+ return new Command("xywh-to-int")
94
+ .description("convert all `db.annotations2.on.xywh` values from float to int")
95
+ .addOption(dryRunOpt)
96
+ .action(async (options, command) => { return await action(options) });
97
+ }
98
+
99
+ export default makeXywhToIntCommand;
@@ -0,0 +1,48 @@
1
+ #!/bin/env bash
2
+
3
+ # MongoDB host
4
+ MONGODB_HOST=127.0.0.1
5
+ # MongoDB port
6
+ MONGODB_PORT=27017
7
+ # MongoDB database name
8
+ MONGODB_DB=aiiinotate
9
+
10
+ # HTTP port for the app
11
+ AIIINOTATE_PORT=4000
12
+ # URL for the app
13
+ AIIINOTATE_HOST=127.0.0.1
14
+ # HTTP scheme: HTTP or HTTPS. should be HTTP in dev and in docker
15
+ AIIINOTATE_SCHEME=http
16
+
17
+ # where to output logs. either: "stdout"|"file"|"stdout+file"|"off"
18
+ AIIINOTATE_LOG_TARGET=stdout
19
+ # directory to save logs to (if AIIINOTATE_LOG_TARGET enables logging to file)
20
+ AIIINOTATE_LOG_DIR=
21
+ # log level. "trace"|"debug"|"info"|"warn"|"error"|"fatal"
22
+ AIIINOTATE_LOG_LEVEL=debug
23
+
24
+
25
+ # max number of items to display per result page
26
+ AIIINOTATE_PAGE_SIZE=5000
27
+ # "true"|"false". strict error throwing when inserting annotations. equivalent
28
+ # to `throwOnCanvasIndexError=true` (raise an error if an annotation's target
29
+ # manifest can't be found) and `throwOnXywhError=true` (raise an error if you
30
+ # can't extract a bounding box for an annotation's target). use in controlled
31
+ # environments where you know precisely the structure of your annotations and
32
+ # where you know that your manifests are accessible through HTTP.
33
+ AIIINOTATE_STRICT_MODE=false
34
+
35
+ # IGNORE
36
+ AIIINOTATE_BASE_URL="$AIIINOTATE_SCHEME://$AIIINOTATE_HOST:$AIIINOTATE_PORT"
37
+ # IGNORE
38
+ MONGODB_CONNSTRING="mongodb://$MONGODB_HOST:$MONGODB_PORT/$MONGODB_DB"
39
+ # IGNORE
40
+ MONGODB_DB_TEST="${MONGODB_DB}_test"
41
+ # IGNORE
42
+ MONGODB_CONNSTRING_TEST="mongodb://$MONGODB_HOST:$MONGODB_PORT/$MONGODB_DB_TEST"
43
+
44
+ # public URL root following `scheme://host`, used to build to root of the IDs of
45
+ # aiiinotate data. In production, set this to your domain name. in dev, set to http://localhost, or anything you want.
46
+ # it should be accessible through HTTP in production, but not necessarily:
47
+ # the app doesn't access this URL in HTTP queries when running.
48
+ AIIINOTATE_PUBLIC_URL="$AIIINOTATE_BASE_URL"
@@ -0,0 +1,39 @@
1
+ # syntax=docker/dockerfile:1
2
+ FROM node:23.11
3
+
4
+ # aiiinotate port
5
+ ARG PORT
6
+ ENV PORT=${PORT}
7
+
8
+ # path to the .env file
9
+ ARG ENV_PATH
10
+ ENV ENV_PATH="$ENV_PATH"
11
+
12
+ # set up environment
13
+ ENV DIR=/aiiinotate
14
+ ENV TERM=linux
15
+ ENV NPM_BIN=/aiiinotate/node_modules/.bin
16
+ SHELL ["/bin/bash", "-c"]
17
+
18
+ # root of the app in the docker container
19
+ WORKDIR ${DIR}
20
+ # copy the docker .env in the docker container
21
+ COPY "$ENV_PATH" ${DIR}/.env
22
+
23
+ # install iproute and curl for debug
24
+ RUN apt-get update
25
+ RUN apt install curl iproute2 -y
26
+
27
+ # create a npm package in $DIR to run aiiinotate from
28
+ RUN npm init -y
29
+ # install the app as an NPM library. we do a global install as it saves us from issues with `$PATH`.
30
+ RUN npm i aiiinotate
31
+
32
+ # expose the used port
33
+ EXPOSE ${PORT}
34
+
35
+ # run the migrations and start the app.
36
+ # ./node_modules/.bin/aiiinotate is to be able to use the `aiiinotate` cli without doing a global install of `aiiinotate`
37
+ # NOTE migrations must be done in CMD: they need the mongo service to be running.
38
+ CMD ${NPM_BIN}/dotenvx run -f ${DIR}/.env -- ${NPM_BIN}/aiiinotate migrate apply && \
39
+ ${NPM_BIN}/dotenvx run -f ${DIR}/.env -- ${NPM_BIN}/aiiinotate serve prod;
@@ -0,0 +1,5 @@
1
+ # Docker setup
2
+
3
+ This folder contains a `docker-compose` for an aiiinotate+MongoDB integration, with volume persistence and a script to import data.
4
+
5
+ View the documentation for aiiinotate Docker setup [here](https://github.com/Aikon-platform/aiiinotate/blob/main/docs/docker.md)
@@ -0,0 +1,49 @@
1
+ x-proxy-settings: &proxy-settings
2
+ HTTP_PROXY: ${HTTP_PROXY:-}
3
+ HTTPS_PROXY: ${HTTPS_PROXY:-}
4
+ NO_PROXY: "localhost,127.0.0.1,mongo,web,.aiiinotate_network"
5
+
6
+ services:
7
+ mongo:
8
+ image: mongo:8
9
+ restart: always
10
+ networks:
11
+ - aiiinotate_network
12
+ volumes:
13
+ - mongodata:/data/db
14
+ environment:
15
+ <<: *proxy-settings
16
+ healthcheck:
17
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
18
+ interval: 10s
19
+ timeout: 5s
20
+ retries: 5
21
+
22
+ aiiinotate:
23
+ build:
24
+ context: ..
25
+ dockerfile: docker/Dockerfile
26
+ args:
27
+ PORT: ${AIIINOTATE_PORT}
28
+ ENV_PATH: ../docker/.env
29
+ # NOTE: be careful to point to docker/.env, not to config/.env, otherwise you will have very, very annoying errors.
30
+ env_file:
31
+ - ../docker/.env
32
+ ports:
33
+ - "${AIIINOTATE_PORT}:${AIIINOTATE_PORT}"
34
+ depends_on:
35
+ mongo:
36
+ condition: service_healthy
37
+ networks:
38
+ - aiiinotate_network
39
+ environment:
40
+ <<: *proxy-settings
41
+ restart: always
42
+
43
+
44
+ volumes:
45
+ mongodata:
46
+
47
+ networks:
48
+ aiiinotate_network:
49
+ driver: bridge