@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.
- package/LICENSE +661 -0
- package/README.md +206 -0
- package/cli/export.js +118 -0
- package/cli/import.js +111 -0
- package/cli/index.js +45 -0
- package/cli/migrate.js +131 -0
- package/cli/serve.js +29 -0
- package/cli/utils/fastifyClient.js +86 -0
- package/cli/utils/io.js +225 -0
- package/cli/utils/mongoClient.js +21 -0
- package/cli/utils/progressbar.js +86 -0
- package/cli/xywhToInt.js +99 -0
- package/config/.env.template +48 -0
- package/docker/Dockerfile +39 -0
- package/docker/README.md +5 -0
- package/docker/docker-compose.yaml +49 -0
- package/docker/docker.sh +43 -0
- package/docker/docker_aiiinotate_import.sh +62 -0
- package/docs/api.md +381 -0
- package/docs/cli.md +132 -0
- package/docs/dev_documentation/dev_architecture.md +88 -0
- package/docs/dev_documentation/dev_db.md +58 -0
- package/docs/dev_documentation/dev_iiif_compatibility.md +43 -0
- package/docs/dev_documentation/dev_notes_quirks_and_troubleshooting.md +143 -0
- package/docs/docker.md +98 -0
- package/docs/includes/report_benchmark_aiiinotate_2026-05-28-02:50:48_7steps.png +0 -0
- package/docs/scalability.md +34 -0
- package/docs/specifications/0_w3c_open_annotations.md +332 -0
- package/docs/specifications/1_w3c_web_annotations.md +577 -0
- package/docs/specifications/2_iiif_apis.md +428 -0
- package/docs/specifications/3_iiif_annotations.md +103 -0
- package/docs/specifications/4_search_api.md +135 -0
- package/docs/specifications/5_sas.md +119 -0
- package/docs/specifications/6_mirador.md +119 -0
- package/docs/specifications/7_aikon.md +137 -0
- package/docs/specifications/include/presentation_2.0.webp +0 -0
- package/docs/specifications/include/presentation_2.0_white.png +0 -0
- package/docs/specifications/include/presentation_3.0.png +0 -0
- package/docs/specifications/include/presentation_3.0_resize.png +0 -0
- package/eslint.config.js +30 -0
- package/migrations/baseConfig.js +57 -0
- package/migrations/manageIndex.js +55 -0
- package/migrations/migrate-mongo-config-main.js +8 -0
- package/migrations/migrate-mongo-config-test.js +8 -0
- package/migrations/migrationScripts/20250825185706-collections.js +48 -0
- package/migrations/migrationScripts/20250826194832-annotations2-schema.js +42 -0
- package/migrations/migrationScripts/20250904080710-annotations2-indexes.js +69 -0
- package/migrations/migrationScripts/20251002141951-manifests2-schema.js +43 -0
- package/migrations/migrationScripts/20251006212110-manifests2-indexes.js +35 -0
- package/migrations/migrationTemplate.js +25 -0
- package/package.json +82 -0
- package/scripts/get_version.py +12 -0
- package/scripts/run.sh +36 -0
- package/scripts/setup_mongodb.sh +70 -0
- package/scripts/setup_node.sh +15 -0
- package/scripts/update_version.py +30 -0
- package/scripts/utils.sh +65 -0
- package/src/app.js +116 -0
- package/src/constants.js +73 -0
- package/src/data/annotations/annotations2.js +681 -0
- package/src/data/annotations/annotations3.js +28 -0
- package/src/data/annotations/routes.js +335 -0
- package/src/data/annotations/routes.test.js +271 -0
- package/src/data/collectionAbstract.js +283 -0
- package/src/data/index.js +29 -0
- package/src/data/manifests/manifests2.js +378 -0
- package/src/data/manifests/manifests2.test.js +53 -0
- package/src/data/manifests/manifests3.js +23 -0
- package/src/data/manifests/routes.js +122 -0
- package/src/data/manifests/routes.test.js +70 -0
- package/src/data/routes.js +181 -0
- package/src/data/routes.test.js +166 -0
- package/src/db/index.js +50 -0
- package/src/fixtures/annotations.js +41 -0
- package/src/fixtures/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
- package/src/fixtures/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
- package/src/fixtures/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
- package/src/fixtures/data/annotations2Invalid.jsonld +39 -0
- package/src/fixtures/data/annotations2SvgValid.jsonld +81 -0
- package/src/fixtures/data/annotations2Valid.jsonld +39 -0
- package/src/fixtures/data/bnf_invalid_manifest.json +2806 -0
- package/src/fixtures/data/bnf_valid_manifest.json +2817 -0
- package/src/fixtures/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
- package/src/fixtures/generate.js +181 -0
- package/src/fixtures/index.js +69 -0
- package/src/fixtures/manifests.js +14 -0
- package/src/fixtures/utils.js +37 -0
- package/src/schemas/index.js +100 -0
- package/src/schemas/schemasBase.js +19 -0
- package/src/schemas/schemasPresentation2.js +410 -0
- package/src/schemas/schemasPresentation3.js +33 -0
- package/src/schemas/schemasResolver.js +71 -0
- package/src/schemas/schemasRoutes.js +318 -0
- package/src/server.js +25 -0
- package/src/types.js +97 -0
- package/src/utils/iiif2Utils.js +332 -0
- package/src/utils/iiif2Utils.test.js +146 -0
- package/src/utils/iiif3Utils.js +0 -0
- package/src/utils/iiifUtils.js +18 -0
- package/src/utils/logger.js +119 -0
- package/src/utils/routeUtils.js +137 -0
- package/src/utils/svg.js +417 -0
- package/src/utils/testUtils.js +289 -0
- package/src/utils/utils.js +403 -0
package/docker/docker.sh
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -e;
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
6
|
+
|
|
7
|
+
# docker specific config
|
|
8
|
+
ENV_DOCKER="$SCRIPT_DIR/.env"
|
|
9
|
+
|
|
10
|
+
if [ ! -f "$ENV_DOCKER" ];
|
|
11
|
+
then echo ".env file not found at '$ENV_DOCKER'. exiting..."; exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
build_containers () {
|
|
15
|
+
docker compose build;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
start_containers() {
|
|
19
|
+
docker compose up -d;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
stop_containers() {
|
|
23
|
+
docker compose down;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
case "$1" in
|
|
27
|
+
start)
|
|
28
|
+
start_containers
|
|
29
|
+
;;
|
|
30
|
+
stop)
|
|
31
|
+
stop_containers
|
|
32
|
+
;;
|
|
33
|
+
build)
|
|
34
|
+
stop_containers
|
|
35
|
+
build_containers
|
|
36
|
+
start_containers
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
echo "Usage: $0 {build|start|stop}"
|
|
40
|
+
exit 1
|
|
41
|
+
;;
|
|
42
|
+
esac;
|
|
43
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# this script imports AnnotationLists from a directory
|
|
4
|
+
# in an aiiinotate instance. the directory must only contain
|
|
5
|
+
# AnnotationLists, with all files at the root of the directory.
|
|
6
|
+
#
|
|
7
|
+
# USAGE: bash docker_aiiinotate_import.sh <"annotations"|"manifests"> <path/to/directory>
|
|
8
|
+
|
|
9
|
+
# "annotation" or "manifest": the type of data to import.
|
|
10
|
+
DATATYPE="$1"
|
|
11
|
+
|
|
12
|
+
# directory containing all files we want to import into aiiinotate
|
|
13
|
+
HOST_DIR=$(echo "$2" | sed -e "s~/$~~g") # sed removes trailing "/"
|
|
14
|
+
|
|
15
|
+
CONTAINER="docker-aiiinotate-1"
|
|
16
|
+
|
|
17
|
+
EXEC="docker exec $CONTAINER"
|
|
18
|
+
NODE_BIN=$($EXEC realpath node_modules/.bin)
|
|
19
|
+
DOTENVX_BIN=$($EXEC realpath $NODE_BIN/dotenvx)
|
|
20
|
+
AIIINOTATE_BIN=$($EXEC realpath $NODE_BIN/aiiinotate)
|
|
21
|
+
|
|
22
|
+
# .env file in container.
|
|
23
|
+
CONTAINER_ENV_FILE=$($EXEC realpath .env)
|
|
24
|
+
# directory where files in $IMPORT_DIR will be stored in the container.
|
|
25
|
+
CONTAINER_DIR="import_files"
|
|
26
|
+
# file listing absolute paht to contents of $CONTAINER_DIR
|
|
27
|
+
CONTAINER_INDEX_FILE=import_files.txt
|
|
28
|
+
|
|
29
|
+
[[ "$DATATYPE" != "annotation" && "$DATATYPE" != "manifest" ]] && {
|
|
30
|
+
echo "error: DATATYPE must be 'annotation' or 'manifest' (got: '$DATATYPE')"
|
|
31
|
+
exit 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
[ ! -d "$HOST_DIR" ] && {
|
|
35
|
+
echo "error: import directory '$HOST_DIR' not found ! exiting";
|
|
36
|
+
exit 1
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# recreate CONTAINER_DIR
|
|
40
|
+
$EXEC bash -c "[ -d \"$CONTAINER_DIR\" ] && rm -rf \"$CONTAINER_DIR\"";
|
|
41
|
+
$EXEC mkdir "$CONTAINER_DIR"
|
|
42
|
+
|
|
43
|
+
# covert paths to abspath
|
|
44
|
+
CONTAINER_DIR=$($EXEC realpath "$CONTAINER_DIR")
|
|
45
|
+
CONTAINER_INDEX_FILE=$($EXEC realpath "$CONTAINER_INDEX_FILE")
|
|
46
|
+
|
|
47
|
+
# copy all files in $HOST_DIR to the docker in $CONTAINER_DIR.
|
|
48
|
+
# if a file in $HOST_DIR is not found, exit.
|
|
49
|
+
for fp in "$HOST_DIR"/*; do
|
|
50
|
+
[ ! -f "$fp" ] && {
|
|
51
|
+
echo "file not found on host: '$fp'. exiting.";
|
|
52
|
+
exit 1
|
|
53
|
+
}
|
|
54
|
+
docker container cp "$fp" "$CONTAINER":"$CONTAINER_DIR"
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
$EXEC bash -c "echo \$(find \"$CONTAINER_DIR\"/* -type f | xargs readlink -f > import_files.txt)"
|
|
58
|
+
$EXEC cat "$CONTAINER_INDEX_FILE"
|
|
59
|
+
|
|
60
|
+
$EXEC bash -c \
|
|
61
|
+
"$DOTENVX_BIN run -f \"$CONTAINER_ENV_FILE\" -- \
|
|
62
|
+
\"$AIIINOTATE_BIN\" import \"$DATATYPE\" -i 2 -f \"$CONTAINER_INDEX_FILE\""
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
This document describes **aiiinotate**'s API.
|
|
4
|
+
|
|
5
|
+
## Introductory notes
|
|
6
|
+
|
|
7
|
+
### Terminology
|
|
8
|
+
|
|
9
|
+
In the docs below,
|
|
10
|
+
|
|
11
|
+
- `Parameters` describes route parameters (dynamic segments of a route's URL).
|
|
12
|
+
- `Query` describes the query string in a key-value format
|
|
13
|
+
|
|
14
|
+
### IIIF Version
|
|
15
|
+
|
|
16
|
+
**aiiinotate** is meant to be able to handle both IIIF presentation APIs: the most common [2.x](https://iiif.io/api/presentation/2.1) and the more recent [3.x](https://iiif.io/api/presentation/3.0). Both APIs define a data structure for manifests, annotations, lists of annotations and collections of manifests.
|
|
17
|
+
|
|
18
|
+
**HOWEVER, in aiiinotate, v2 and v3 data are isolated**: they form two separate collections, and no conversion is done between IIIF 2.x and 3.x data. This means that:
|
|
19
|
+
- **when communicating with aiiinotate**, you must specify a **IIIF presentation version in the query URL**. In the docs, this is described by the `iiif_version` keyword.
|
|
20
|
+
- **when inserting/updating data**, the data structure you provide must match the URL's `iiif_version`: you can't insert an annotation in v3 if your `iiif_version` is `2`.
|
|
21
|
+
- **TLDR**:
|
|
22
|
+
- your data must match the `iiif_version` argument
|
|
23
|
+
- if you insert an Annotation following the API v3.x, you can't search for it using `iiif_version=2`.
|
|
24
|
+
|
|
25
|
+
This is because
|
|
26
|
+
- the IIIF standard is quite complex and there are breaking changes between v2 and v3
|
|
27
|
+
- handling conversions between v2 and v3 is error prone, would increase calculations and slow the app down
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Generic routes
|
|
32
|
+
|
|
33
|
+
### IIIF search API
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
GET /search-api/{iiif_version}/manifests/{manifest_short_id}/search
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Implementation of the [IIIF Search API](https://iiif.io/api/search/2.0/), to search one or several annotations within a manifest.
|
|
40
|
+
|
|
41
|
+
#### Request
|
|
42
|
+
|
|
43
|
+
- Parameters:
|
|
44
|
+
- `iiif_version` (`2 | 3`): the IIIF aearch API version. 2 is for IIIF Presentation API 3.x, 1 is for IIIF Presentation API 2.x
|
|
45
|
+
- `manifest_short_id` (`string`): the ID of the manifest. See the *IIIF URIs* section.
|
|
46
|
+
- Query:
|
|
47
|
+
- `q` (`string`): query string.
|
|
48
|
+
- if `iiif_version=1`, `q` is searched in the fields: `@id`, `resource.@id` or `resource.chars` fields
|
|
49
|
+
- `motivation` (`painting | non-painting | commenting | describing | tagging | linking`): values for the `motivation` field of an annotation
|
|
50
|
+
- `canvasMin` (`number`): a positive integer
|
|
51
|
+
- `canvasMax` (`number`): a positive integer
|
|
52
|
+
- `canvasMax` must be greater than `canvasMin`
|
|
53
|
+
- if `canvasMax` is undefined, then we will only return the annotations that target a canvas at the `canvasMin` position in its manifest.
|
|
54
|
+
- `page` (`number`): results are paginated. This specifies the page number
|
|
55
|
+
- `pageSize` (`number`): number of annotations to display per page. Defaults to `process.env.AIIINOTATE_PAGE_SIZE`.
|
|
56
|
+
- `onlyIds` (`boolean`): return just the value of `@id` fields of matched annotations as a `string[]` instead of returning all the annotations. If `onlyIds=true`, there is no pagination, `page` and `pageSize` won't have any effect.
|
|
57
|
+
|
|
58
|
+
#### Response
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
AnnotationList | AnnotationPage | string[]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- if `onlyIds=true`, return a `string[]` (array of the IDs of all matched annotations)
|
|
65
|
+
- otherwise,
|
|
66
|
+
- if `iiif_version` is `1`, return an `AnnotationList`
|
|
67
|
+
- else, return an `AnnotationPage`.
|
|
68
|
+
|
|
69
|
+
#### Notes
|
|
70
|
+
|
|
71
|
+
- `canvasMin`, `canvasMax` and `onlyIds` are non-standard query parameters that are NOT part of the IIIF Search API specification.
|
|
72
|
+
- If `q` and `motivation` are unused, it will return all annotations for the manifest
|
|
73
|
+
- Only exact matches are allowed for `q` and `motivation`
|
|
74
|
+
- About `canvasMin` and `canvasMax`:
|
|
75
|
+
- they are used to search for annotations that target a range of canvases: for example, fetch all anotations between pages 3 and 30 of a manuscript.
|
|
76
|
+
- they are **0-indexed**: the 1st canvas of a manifest is indexed `0`, the 2nd is indexed `1`...
|
|
77
|
+
- See section [Create/update an annotation](#createupdate-an-annotation) for more information and possible issues with canvas indexes.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Delete an annotation or a manifest
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
DELETE /{collection_name}/{iiif_version}/delete
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Request
|
|
88
|
+
|
|
89
|
+
- Parameters:
|
|
90
|
+
- `collection_name` (`annotations | manifests`): delete an annotation or a manifest
|
|
91
|
+
- `iiif_version` (`2 | 3`): IIIF presentation version
|
|
92
|
+
- Query:
|
|
93
|
+
- if `collection_name = manifests`:
|
|
94
|
+
- `uri`: the full URI of the manifest to delete
|
|
95
|
+
- `manifestShortId`: the manifest's identifier
|
|
96
|
+
- if `collection_name = annotation`:
|
|
97
|
+
- `uri`: the full URI of the annotation to delete
|
|
98
|
+
- `manifestShortId`: a manifest's identifier, to delete all annotations for a manifest
|
|
99
|
+
- `canvasUri`: the full URI to an annotation's target canvas, to delete all annotatons for the canvas
|
|
100
|
+
- `tag`: delete all annotations with a certain tag in a manifest
|
|
101
|
+
- MUST be used in conjunction with `manifestShortId` to indicate on which manifest to delete annotations by tag: `delete?tag=<your-tag>&manifestShortId=<your-manifest-id>`
|
|
102
|
+
|
|
103
|
+
#### Response
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
{ deletedCount: <integer> }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Manifests routes
|
|
112
|
+
|
|
113
|
+
### Get an index of all manifests
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
GET /manifests/{iiif_version}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Returns a Collection of all manifests in your **aiiinotate** instance.
|
|
120
|
+
|
|
121
|
+
#### Request
|
|
122
|
+
|
|
123
|
+
- Parameters:
|
|
124
|
+
- `iiif_version` (`2 | 3`): the IIIF Presentation API version
|
|
125
|
+
|
|
126
|
+
#### Response
|
|
127
|
+
|
|
128
|
+
A IIIF `Collection`, following the IIIF Presentation API 2 or 3, depending of the value of `iiif_version`.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### Insert a manifest
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
POST /manifests/{iiif_version}/create
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Request
|
|
139
|
+
|
|
140
|
+
- Parameters:
|
|
141
|
+
- `iiif_version` (`2 | 3`): the IIIF Presentation API version of your manifest
|
|
142
|
+
- Body (`JSON`): the manifest to index in the database
|
|
143
|
+
|
|
144
|
+
#### Response
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
{
|
|
148
|
+
insertedIds: string[],
|
|
149
|
+
preExistingIds: string[],
|
|
150
|
+
rejectedIds: []
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- `insertedIds`: the list of IDs of inserted manifests
|
|
155
|
+
- `preExisingIds`: the IDs of manifests that were aldready in the database
|
|
156
|
+
- `rejectedIds`: the IDs of manifests on which an error occurred
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Annotation routes
|
|
161
|
+
|
|
162
|
+
### Get all annotations for a canvas
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
GET /annotations/{iiif_version}/search
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Request
|
|
169
|
+
|
|
170
|
+
- Parameters:
|
|
171
|
+
- `iiif_version` (`2 | 3`): the IIIF Presentation API of your manifests
|
|
172
|
+
- Query:
|
|
173
|
+
- `canvasUri` (`string`): the URI of the target canvas
|
|
174
|
+
- `page` (`number`): results are paginated. Specifies the page number.
|
|
175
|
+
- `pageSize` (`number`): number of items per page. Defaults to `process.env.AIIINOTATE_PAGE_SIZE`.
|
|
176
|
+
|
|
177
|
+
#### Response
|
|
178
|
+
|
|
179
|
+
Results are paginated.
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
AnnotationList | AnnotationPage
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### Count annotations
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
GET /annotations/{iiif_version}/count
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### Request
|
|
194
|
+
|
|
195
|
+
- Parameters:
|
|
196
|
+
- `iiif_version` (`2 | 3`): the IIIF Presentation API of your manifests
|
|
197
|
+
- Query:
|
|
198
|
+
- `uri` (`string`): the annotation's `@id`
|
|
199
|
+
- `canvasUri` (`string`): the annotation's target canvas (`on.full`)
|
|
200
|
+
- `manifestShortId` (`string`): the short ID of the annotation's target manifest (`on.manifestShortId`)
|
|
201
|
+
|
|
202
|
+
#### Response
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
{ count: integer }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### Get a single annotation
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
GET /data/{iiif_version}/{manifest_short_id}/annotation/{annotation_short_id}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
This route allows to query an annotation by its ID by defering its `@id | id` field. This URL follows the IIIF specification
|
|
217
|
+
|
|
218
|
+
#### Request
|
|
219
|
+
|
|
220
|
+
- Parameters:
|
|
221
|
+
- `iiif_version` (`2 | 3`): the IIIF version of the annotation
|
|
222
|
+
- `manifest_short_id` (`string`): the identifier of the manifest the annotation is related to
|
|
223
|
+
- `annotation_short_id`: the unique part of the annotation URL
|
|
224
|
+
|
|
225
|
+
#### Response
|
|
226
|
+
|
|
227
|
+
`Object`: the annotation. Its format follows the IIIF Presentation specification 2 or 3, based on the value of `iiif_version`.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### Create/update an annotation
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
POST /annotations/{iiif_version}/{action}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Create or update a single annotation
|
|
238
|
+
|
|
239
|
+
#### Request
|
|
240
|
+
|
|
241
|
+
- Parameters:
|
|
242
|
+
- `iiif_version` (`2 | 3`): the IIIF version of the annotation
|
|
243
|
+
- `action` (`create | update`): the action to perform: create or update an annotation
|
|
244
|
+
- Query:
|
|
245
|
+
- `throwOnCanvasIndexError` (`boolean`): throw an error if there's a problem fetching the annotation's target canvas index. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
|
|
246
|
+
- `throwOnXywhError` (`boolean`): throw an error if target bounding box calculation fails. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
|
|
247
|
+
- Body (`Object`): a IIIF annotation that follows the IIIF Presentation API 2 or 3 (depending on the value of `iiif_version`)
|
|
248
|
+
|
|
249
|
+
#### Response
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
{
|
|
253
|
+
insertedIds: string[],
|
|
254
|
+
preExistingIds: string[],
|
|
255
|
+
rejectedIds: string[],
|
|
256
|
+
fetchErrorIds: string[]
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### Insert several annotations
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
POST /annotations/{iiif_version}/createMany
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Batch insert multiple annotations.
|
|
269
|
+
|
|
270
|
+
#### Request
|
|
271
|
+
|
|
272
|
+
- Parameters:
|
|
273
|
+
- `iiif_version` (`2 | 3`): the IIIF version of the annotation
|
|
274
|
+
- Query:
|
|
275
|
+
- `throwOnCanvasIndexError` (`boolean`): throw an error if there's a problem fetching the annotation's target canvas index. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
|
|
276
|
+
- `throwOnXywhError` (`boolean`): throw an error if target bounding box calculation fails. See [Appendix 1](#appendix-1-annotation-canvas-index-and-bounding-box-calculation)
|
|
277
|
+
- Body: either:
|
|
278
|
+
- a full `AnnotationList | AnnotationPage` embedded in the body (type must match `iiif_version`: AnnotationPage for IIIF 3, AnnotationList for IIIF 2).
|
|
279
|
+
- `AnnotationList[] | AnnotationPage[]` (type must match `iiif_version`): an array of annotation lists or pages
|
|
280
|
+
- `{ uri: string }`: an object containing a reference to an `AnnotationList` or `AnnotationPage`
|
|
281
|
+
- `{ uri: string }[]`: an array of objects containing a reference to an `AnnotationList` or `AnnotationPage`.
|
|
282
|
+
|
|
283
|
+
#### Response
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
{
|
|
287
|
+
insertedIds: string[],
|
|
288
|
+
preExistingIds: string[],
|
|
289
|
+
rejectedIds: []
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### Notes
|
|
294
|
+
|
|
295
|
+
- Calling this route using parallel processes (i.e., `Promise.all`) can cause data races which will cause inserts to fail
|
|
296
|
+
- Be wary of maximum body size, especially when sending AnnotationLists in your body. If possible, using `{ uri: string }` is better.
|
|
297
|
+
- All annotations within a single AnnotationList/Page may have different target canvases or manifests.
|
|
298
|
+
- See **Create/update an annotation**.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Appendix 1: annotation canvas index and bounding box calculation
|
|
303
|
+
|
|
304
|
+
Two non-standard side-effects happen when inserting an annotation:
|
|
305
|
+
- the annotation's target manifest is also fetched and inserted in the database. The annotation's target canvas index is fetched and added to the annotation.
|
|
306
|
+
- calculating the XYWH bounding box of an annotation.
|
|
307
|
+
|
|
308
|
+
Annotations in `aiiinotate` contain nonstandard fields. In IIIF presentation 2.x,
|
|
309
|
+
- `manifestUri`, `manifestShortId`, `canvasIdx`:
|
|
310
|
+
- `annotation.on.manifestUri`: the URI of the manifest on which is an annotation
|
|
311
|
+
- `annotation.on.manifestShortId`: the unique identifier of the manifest on which is an annotation
|
|
312
|
+
- `annotation.on.canvasIdx`: the position of an annotation's target canvas within the target manifest, as an integer. `canvasIdx` is **0-indexed**: the 1st canvas in a manifest is indexed from `0`
|
|
313
|
+
- this relies on reconstructing an annotation's target manifest URL and fetching it. If this process fails, the fields above will be `undefined`.
|
|
314
|
+
- fetching an annotation's target manfest is error prone: it depends on the manifest being available through HTTP, which is not in our control.
|
|
315
|
+
- **you can control error throwing** (throw an error if a canvas index can't be found) using:
|
|
316
|
+
- at route-level, the `throwOnCanvasIndexError` query parameter
|
|
317
|
+
- at app-level, the `AIIINOTATE_STRICT_MODE` env variable.
|
|
318
|
+
- if `AIIINOTATE_STRICT_MODE` or `throwOnCanvasIndexError`, an error will be thrown if an error appears anywhere in the process of fetching the target manifest and populating the `canvasIdx` field.
|
|
319
|
+
- `annotation.on.xywh`: the annotation's bounding box on its target canvas.
|
|
320
|
+
- when inserting an annotation, aiiinotate will attempt to extract its XYWH bounding box and store it as an `[number,number,number,number]`.
|
|
321
|
+
- this only works for `oa:SvgSelectors`, `oa:FragmentSeletors`, `oa:Choice` containing either an `SvgSelector` or a `FragmentSelector`, or a string-target (an URI with a `#xywh=` fragment selector).
|
|
322
|
+
- **you can control error-throwing** (throw an error if a bounding-box can't be calculated) using:
|
|
323
|
+
- at route-level, the `throwOnXywhError` query parameter
|
|
324
|
+
- at app-level, the `AIIINOTATE_STRICT_MODE` env variable.
|
|
325
|
+
|
|
326
|
+
About `throwOnCanvasIndexError`, `throwOnXywhError` and `AIIINOTATE_STRICT_MODE`:
|
|
327
|
+
- as indicated above, fetching an annotation's target canvas index and calculating an annotation's target bounding boxc is error prone.
|
|
328
|
+
- in turn, you can decide wether or not to throw errors using:
|
|
329
|
+
- at route-level, the `throwOnCanvasIndexError` (for canvas index) and `throwOnXywhError` (for bounding box) query parameters
|
|
330
|
+
- at app-level, the `AIIINOTATE_STRICT_MODE` env variable. Setting this to `true` will set the value of `throwOnCanvasIndexError` and `throwOnXywhError` to `true` by default.
|
|
331
|
+
- the value of `AIIINOTATE_STRICT_MODE` can be overridden by setting a value to query parameters `throwOnCanvasIndexError` and `throwOnXywhError`.
|
|
332
|
+
- if those are set to `true`, an error will be thrown if a canvas index can't be found. Otherwise, the values will be set to `undefined`.
|
|
333
|
+
- in controlled environments where you know your manifests will be available and where you rely heavily on the `canvasIdx` field (like AIKON), throwing an error will ensure that the `canvasIdx` field is always defined.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Appendix 2: App logic: URL prefixes
|
|
338
|
+
|
|
339
|
+
URL anatomy is a mix of [SAS endpoints](./specifications/4_sas.md) and IIIF specifications. In turn, we define the following prefixes:
|
|
340
|
+
|
|
341
|
+
- `data`: for all IIIF URIs: URIs of annotations and annotation lists
|
|
342
|
+
- `annotations`: operations on annotations
|
|
343
|
+
- `manifests`: operations on manifests
|
|
344
|
+
- `search-api`: endpoint to access the IIIF search API
|
|
345
|
+
|
|
346
|
+
In turn, URL anatomy is:
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
{host}/{prefix}/{iiif_version}/{slug}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Where:
|
|
353
|
+
- `host`: the host of your app
|
|
354
|
+
- `prefix`: `data | annotations | manifests`
|
|
355
|
+
- `iiif_version`:
|
|
356
|
+
- if `prefix` is `search-api`, `1 | 2`: IIIF Search API version used
|
|
357
|
+
- otherwise, `2 | 3`, the IIIF Presentation API version your data is in
|
|
358
|
+
- `slug`: the rest of the qurty URI
|
|
359
|
+
|
|
360
|
+
There is an extra URL prefix: `schemas`. It is only used internally (not accessible to clients or accessible through HTTP) to define the IDs of all JsonSchemas, so we won't talk about it here.
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Appendix 3: IIIF URIs
|
|
366
|
+
|
|
367
|
+
IIIF URIs in the Presentation 2.1 API are:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
Collection {scheme}://{host}/{prefix}/collection/{name}
|
|
371
|
+
Manifest {scheme}://{host}/{prefix}/{manifest_short_id}/manifest
|
|
372
|
+
Sequence {scheme}://{host}/{prefix}/{manifest_short_id}/sequence/{name}
|
|
373
|
+
Canvas {scheme}://{host}/{prefix}/{manifest_short_id}/canvas/{name}
|
|
374
|
+
Annotation (incl images) {scheme}://{host}/{prefix}/{manifest_short_id}/annotation/{name}
|
|
375
|
+
AnnotationList {scheme}://{host}/{prefix}/{manifest_short_id}/list/{name}
|
|
376
|
+
Range {scheme}://{host}/{prefix}/{manifest_short_id}/range/{name}
|
|
377
|
+
Layer {scheme}://{host}/{prefix}/{manifest_short_id}/layer/{name}
|
|
378
|
+
Content {scheme}://{host}/{prefix}/{manifest_short_id}/res/{name}.{format}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# CLI DOCUMENTATION
|
|
2
|
+
|
|
3
|
+
The CLI is the main interface to interact with aiiinotate: all aiiinotate commands are started from the CLI.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Basics
|
|
8
|
+
|
|
9
|
+
1. **Have your app set up**, either in dev or in prod. See [README](https://github.com/Aikon-platform/aiiinotate/blob/main/README.md) for more info
|
|
10
|
+
|
|
11
|
+
2. **In prod, source your `.env`**. See README.
|
|
12
|
+
|
|
13
|
+
3. **The base command is**:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# in prod
|
|
17
|
+
aiiinotate
|
|
18
|
+
|
|
19
|
+
# in dev
|
|
20
|
+
npm run cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**The command changes with a prod install**, because the package has been installed with `npm`.
|
|
24
|
+
- for the rest of this doc, we use `aiiinotate` instead of `npm run cli`. Use whichever is appropriate to your use case.
|
|
25
|
+
- `npm run cli` runs a bash script that also manages environment sourcing.
|
|
26
|
+
- when using `npm run cli`, you will sometimes need to add `--` to separate different arguments:
|
|
27
|
+
```bash
|
|
28
|
+
npm run cli -- <command> <arguments>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## `serve`: start the app
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# serve the app in dev mode
|
|
37
|
+
aiiinotate serve dev
|
|
38
|
+
|
|
39
|
+
# serve the app in prod mode
|
|
40
|
+
aiiinotate serve prod
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## `migrate`: run database migrations
|
|
46
|
+
|
|
47
|
+
NOTE: **in dev**, run `npm run migrate`, which is a wrapper for `npm run cli migrate`.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# create a new migration
|
|
51
|
+
aiiinotate migrate make --migration-name <your migration name>
|
|
52
|
+
# the equivalent in dev is:
|
|
53
|
+
npm run migrate make -- --migration-name <your migration name>
|
|
54
|
+
|
|
55
|
+
# apply all pending migrations
|
|
56
|
+
aiiinotate migrate apply
|
|
57
|
+
|
|
58
|
+
# revert the last migration
|
|
59
|
+
aiiinotate migrate revert
|
|
60
|
+
|
|
61
|
+
# revert all migrations
|
|
62
|
+
aiiinotate migrate revert-all
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## `import`: import data
|
|
68
|
+
|
|
69
|
+
The CLI can be used to import IIIF annotations or to index IIIF manifests in aiiinotate.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
aiiinotate import <manifests|annotations> -i 2 -f <path/to/import/file.txt>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Where:
|
|
76
|
+
- `<manifests|annotations>` is the kind of data to import: manifests or annotations
|
|
77
|
+
- `-i` `--iiif-version` (`2|3`) is the IIIF Presentation API version of the data to import:
|
|
78
|
+
- if `-i 3`, we import IIIF presentation 3.x data
|
|
79
|
+
- if `-i 2`, we import IIIF presentation 2.x data
|
|
80
|
+
- `-f --file` is a relative or absolute path the the import file. This import file:
|
|
81
|
+
- contains a list of paths to annotation lists, annotation pages or manifests
|
|
82
|
+
- with 1 path per line
|
|
83
|
+
- paths can be relative or absolute
|
|
84
|
+
|
|
85
|
+
NOTE that data to import **MUST MATCH the IIIF version**: if `-i 2`, you can only import `AnnotationLists` following the IIIF Presentation 2.x standard, and note annotations in 3.x format.
|
|
86
|
+
|
|
87
|
+
NOTE that for the moment, import is only implemented for IIIF version 2.
|
|
88
|
+
|
|
89
|
+
Here is an example of import file:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
./annotations/wit91_pdf108_anno194.json
|
|
93
|
+
./annotations/wit70_man70_anno206.json
|
|
94
|
+
./annotations/wit71_man71_anno202.json
|
|
95
|
+
./annotations/wit68_man68_anno198.json
|
|
96
|
+
./annotations/wit72_man72_anno133.json
|
|
97
|
+
./annotations/wit75_man74_anno130.json
|
|
98
|
+
./annotations/wit2_man4_anno4.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## `export`: export data
|
|
104
|
+
|
|
105
|
+
The CLI can be used to export data to JSON.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
aiiinotate export <all|annotations2|manifests2|annotations3|manifests3> [-o /output/dir]?
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Each collection will be written to its own JSON file.
|
|
112
|
+
|
|
113
|
+
Exported JSON data can be imported back in a MongoDB instance using [mongoimport](https://www.mongodb.com/docs/database-tools/mongoimport/):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
mongoimport \
|
|
117
|
+
--host localhost \
|
|
118
|
+
--db <db name> \
|
|
119
|
+
--collection <collection name> \
|
|
120
|
+
--file <./path/to/export.json> \
|
|
121
|
+
--jsonArray
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## `xywhToInt`
|
|
127
|
+
|
|
128
|
+
In versions of aiiinotate `>0.10.3`, the `xywh` bounding box extracted from incoming annotations and saved to database may contain `floats`. This causes errors when reconstructing Cantaloupe URLs from t`xywh`. This command converts all `xywh` containing floats to intgers. It is only implemented for collection `annotations2`.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
aiiinotate run xywh-to-int
|
|
132
|
+
```
|