aiiinotate 0.2.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 +61 -0
- package/cli/import.js +142 -0
- package/cli/index.js +26 -0
- package/cli/io.js +105 -0
- package/cli/migrate.js +123 -0
- package/cli/mongoClient.js +11 -0
- package/docs/architecture.md +88 -0
- package/docs/db.md +38 -0
- package/docs/dev_iiif_compatibility.md +43 -0
- package/docs/endpoints.md +48 -0
- package/docs/progress.md +159 -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 +396 -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 +27 -0
- package/migrations/baseConfig.js +56 -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 +41 -0
- package/migrations/migrationScripts/20250826194832-annotations2-canvas-index.js +31 -0
- package/migrations/migrationScripts/20250904080710-annotations2-schema.js +42 -0
- package/migrations/migrationScripts/20251002141951-manifest2-schema.js +43 -0
- package/migrations/migrationScripts/20251006212110-manifest-unique-index.js +29 -0
- package/migrations/migrationScripts/20251028115614-annotations2-id-index.js +27 -0
- package/migrations/migrationTemplate.js +25 -0
- package/package.json +78 -0
- package/run.sh +70 -0
- package/scripts/_migrations.sh +79 -0
- package/scripts/_setup.js +31 -0
- package/scripts/setup_mongodb.sh +61 -0
- package/scripts/setup_mongodb_migrate.sh +17 -0
- package/scripts/setup_node.sh +15 -0
- package/scripts/utils.sh +192 -0
- package/setup.sh +20 -0
- package/src/app.js +113 -0
- package/src/config/.env.template +22 -0
- package/src/data/annotations/annotations2.js +419 -0
- package/src/data/annotations/annotations3.js +32 -0
- package/src/data/annotations/routes.js +271 -0
- package/src/data/annotations/routes.test.js +180 -0
- package/src/data/collectionAbstract.js +270 -0
- package/src/data/index.js +29 -0
- package/src/data/manifests/manifests2.js +305 -0
- package/src/data/manifests/manifests2.test.js +53 -0
- package/src/data/manifests/manifests3.js +23 -0
- package/src/data/manifests/routes.js +95 -0
- package/src/data/manifests/routes.test.js +69 -0
- package/src/data/routes.js +141 -0
- package/src/data/routes.test.js +117 -0
- package/src/data/utils/iiif2Utils.js +196 -0
- package/src/data/utils/iiif2Utils.test.js +98 -0
- package/src/data/utils/iiif3Utils.js +0 -0
- package/src/data/utils/iiifUtils.js +18 -0
- package/src/data/utils/routeUtils.js +109 -0
- package/src/data/utils/testUtils.js +253 -0
- package/src/data/utils/utils.js +231 -0
- package/src/db/index.js +48 -0
- package/src/fileServer/annotations.js +39 -0
- package/src/fileServer/data/annotationList_aikon_wit9_man11_anno165_all.jsonld +827 -0
- package/src/fileServer/data/annotationList_vhs_wit250_man250_anno250_all.jsonld +37514 -0
- package/src/fileServer/data/annotationList_vhs_wit253_man253_anno253_all.jsonld +20111 -0
- package/src/fileServer/data/annotations2Invalid.jsonld +39 -0
- package/src/fileServer/data/annotations2Valid.jsonld +39 -0
- package/src/fileServer/data/bnf_invalid_manifest.json +2806 -0
- package/src/fileServer/data/bnf_valid_manifest.json +2817 -0
- package/src/fileServer/data/vhs_wit253_man253_anno253_anno-24.json +1 -0
- package/src/fileServer/index.js +64 -0
- package/src/fileServer/manifests.js +14 -0
- package/src/fileServer/utils.js +35 -0
- package/src/schemas/index.js +20 -0
- package/src/schemas/schemasBase.js +47 -0
- package/src/schemas/schemasPresentation2.js +417 -0
- package/src/schemas/schemasPresentation3.js +57 -0
- package/src/schemas/schemasResolver.js +71 -0
- package/src/schemas/schemasRoutes.js +277 -0
- package/src/server.js +22 -0
- package/src/types.js +93 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Endpoints
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## URL prefixes
|
|
6
|
+
|
|
7
|
+
URL anatomy is a mix of [SAS endpoints](./specifications/4_sas.md) and IIIF specifications. In turn, we define the following prefixes:
|
|
8
|
+
|
|
9
|
+
- `data`: for all IIIF URIs: URIs of annotations and annotation lists
|
|
10
|
+
- `annotations`: operations on annotations
|
|
11
|
+
- `manifests`: operations on manifests
|
|
12
|
+
- `search-api`: endpoint to access the IIIF search API
|
|
13
|
+
|
|
14
|
+
In turn, URL anatomy is:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
{host}/{prefix}/{iiif_version}/{slug}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Where:
|
|
21
|
+
- `host`: the host of your app
|
|
22
|
+
- `prefix`: `data | annotations | manifests`
|
|
23
|
+
- `iiif_version`:
|
|
24
|
+
- if `prefix` is `search-api`, `1 | 2`: IIIF Search API version used
|
|
25
|
+
- otherwise, `2 | 3`, the IIIF Presentation API version your data is in
|
|
26
|
+
- `slug`: the rest of the qurty URI
|
|
27
|
+
|
|
28
|
+
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.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## IIIF URIs
|
|
33
|
+
|
|
34
|
+
IIIF URIs in the Presentation 2.1 API are:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Collection {scheme}://{host}/{prefix}/collection/{name}
|
|
38
|
+
Manifest {scheme}://{host}/{prefix}/{identifier}/manifest
|
|
39
|
+
Sequence {scheme}://{host}/{prefix}/{identifier}/sequence/{name}
|
|
40
|
+
Canvas {scheme}://{host}/{prefix}/{identifier}/canvas/{name}
|
|
41
|
+
Annotation (incl images) {scheme}://{host}/{prefix}/{identifier}/annotation/{name}
|
|
42
|
+
AnnotationList {scheme}://{host}/{prefix}/{identifier}/list/{name}
|
|
43
|
+
Range {scheme}://{host}/{prefix}/{identifier}/range/{name}
|
|
44
|
+
Layer {scheme}://{host}/{prefix}/{identifier}/layer/{name}
|
|
45
|
+
Content {scheme}://{host}/{prefix}/{identifier}/res/{name}.{format}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
package/docs/progress.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Dev progress
|
|
2
|
+
|
|
3
|
+
We mostly talk about which routes are done here
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Done
|
|
8
|
+
|
|
9
|
+
### Routes
|
|
10
|
+
|
|
11
|
+
Routes are only implemented with IIIF Presentation API 2.x, not with the 3.0 version.
|
|
12
|
+
|
|
13
|
+
#### Generic routes
|
|
14
|
+
|
|
15
|
+
- `GET /search-api/:iiifSearchVersion/manifests/:manifestShortId/search`: search API
|
|
16
|
+
|
|
17
|
+
#### Annotations routes
|
|
18
|
+
|
|
19
|
+
- `GET /annotations/:iiifPresentationVersion/search`: get all annotations for a canvas URI
|
|
20
|
+
- `POST /annotations/:iiifPresentationVersion/create`: create 1 annotation
|
|
21
|
+
- `POST /annotations/:iiifPresentationVersion/createMany`: create several annotations
|
|
22
|
+
- `POST /annotations/:iiifPresentationVersion/update`: update 1 annotation
|
|
23
|
+
- `DELETE /annotations/:iiifPresentationVersion/delete`: delete annotations, either by their `@id`, trget canvas URI (`on.full`), or their `on.manifestShortId`
|
|
24
|
+
|
|
25
|
+
=> all create/update/delete annotation routes are done !
|
|
26
|
+
|
|
27
|
+
#### Manifests routes
|
|
28
|
+
|
|
29
|
+
- `POST /manifests/:iiifPresentationVersion/create`: create a single manifest, either by including the manifest in the body or its URI
|
|
30
|
+
- `DELETE /manifests/:iiifPresentationVersion/delete`: delete a single manifest
|
|
31
|
+
- `GET /manifests/:iiifPresentationVersion`: return an index of all manifests as a collection
|
|
32
|
+
|
|
33
|
+
### Non-routes
|
|
34
|
+
|
|
35
|
+
- `manifests2`: `insert`, `insertMany` internal behaviours
|
|
36
|
+
- fetching and inserting manifests related to an annotation when using inserting annotations.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
|
|
42
|
+
### Uniqueness
|
|
43
|
+
|
|
44
|
+
As of writing (09.10.25), there are no uniqueness constraints on annotations. There is only a uniqueness constraint on collection `manifest2` on field `manifest2.@id` (the ID of a manifest).
|
|
45
|
+
|
|
46
|
+
Ideally, we would want to avoid having duplicate annotations in the database. This is more complicated in practice: at least for `annotions2`, an annotation's `@id` field is re-generated and a random part (an UUID) is generated at insert time. This means that, when trying to store the same annotation (with the same `@id`), the `@id` is changed, and so a different value is inserted.
|
|
47
|
+
|
|
48
|
+
This means that we can't have a uniqueness constraint on `@id` or `id` fields of annotations. Another option would be to have a uniqueness constraint on annotation targets (no 2 annotations can annotate the same region), but this behaviour seems brittle in practice, so it's not yet implemented.
|
|
49
|
+
|
|
50
|
+
### Concurrency
|
|
51
|
+
|
|
52
|
+
For clients, concurrency/parrallelization (i.e., with JS `Promise.all()`) on insert/update should be avoided because it can cause a data race: several processes can attempt to write the same thing in parrallel.
|
|
53
|
+
|
|
54
|
+
For example, when inserting annotations, the manifests related to each annotation are inserted in parrallel. Since this is a side effect, 2 processes may unknowingly try to insert the same manifest in the database, which causes a uniqueness constraint to fail. This error can be hard to debug, so it's best to avoid concurrency at write time.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Dev quirks
|
|
59
|
+
|
|
60
|
+
Sometimes, node/fastify can be weird. When scratching your head at dev errors, look here:)
|
|
61
|
+
|
|
62
|
+
### Error swallowing at app build
|
|
63
|
+
|
|
64
|
+
Errors that happen when a plugin is being registered will cause the app's startup to fail, without necessarily throwing an error.
|
|
65
|
+
|
|
66
|
+
This is especially true for test cases that build the fastify app:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
test
|
|
70
|
+
=> build fastify app
|
|
71
|
+
=> build step fails silently
|
|
72
|
+
=> test fails
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### Troubleshooting
|
|
76
|
+
|
|
77
|
+
- normal behaviour: when a runtime error happens, the failing test will log the error on the console
|
|
78
|
+
- what this error looks like:
|
|
79
|
+
- tests fail **very quickly**,
|
|
80
|
+
- without throwing an error, seemingly without even running the test suite
|
|
81
|
+
- the proces doesn't exit, although all tests have failed
|
|
82
|
+
- when `npm run test` fails like this, run `npm run start`. See if normal startup throws an error
|
|
83
|
+
- NOTE: normal startup not throwing does NOT mean that the build step necessarily worked
|
|
84
|
+
|
|
85
|
+
#### Possible help/solutions
|
|
86
|
+
|
|
87
|
+
- find a way to stisfyingly use `try...catch` at plugin registration.
|
|
88
|
+
- look into these issues: [2694](https://github.com/fastify/fastify/issues/2694)
|
|
89
|
+
- in particular, it may be an issue specific to async plugins ?
|
|
90
|
+
|
|
91
|
+
### Route response schema definition
|
|
92
|
+
|
|
93
|
+
For some reason, route schema definition is much less flexible for responses than for queries.
|
|
94
|
+
|
|
95
|
+
#### The problem
|
|
96
|
+
|
|
97
|
+
**Query schemas**: In queries (`schema.params` or `schema.querystring`), fastify is very permissive. You can use unresolved schemas (with `$ref`), save entire schemas as JsonSchemas ...
|
|
98
|
+
|
|
99
|
+
**Response schemas**: Response schemas have more constraints.
|
|
100
|
+
- **response schemas are defined at route level** and cannot be stored as full JsonSchemas:
|
|
101
|
+
```js
|
|
102
|
+
// this will be an invalid response schema: it is trying to store a complete response schema as a JsonSchema
|
|
103
|
+
fastify.addSchema({
|
|
104
|
+
$id: makeSchemaUri("routeResponsePost"),
|
|
105
|
+
properties: {
|
|
106
|
+
200: { ... },
|
|
107
|
+
500: { ... },
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// this is a fixed version: an object containing schemas, not a full JsonSchema, to be used inside a Route definition
|
|
112
|
+
schema: {
|
|
113
|
+
response: {
|
|
114
|
+
200: { ... },
|
|
115
|
+
500: { ... }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// if you want to reuse a schema, store it as a JS object and import it.
|
|
119
|
+
```
|
|
120
|
+
- **unresolved response schemas (`$ref`) are forbidden**. You cannot use `$ref`. In the app, use `fastify.schemasResolver` to resolve the schema to a plain `JsonSchema` without `$ref`.
|
|
121
|
+
|
|
122
|
+
#### The fix
|
|
123
|
+
|
|
124
|
+
In short, here's **how to use shared schemas in responses**:
|
|
125
|
+
1. Define payload schemas for different response cases:
|
|
126
|
+
```js
|
|
127
|
+
// in case of a POST success
|
|
128
|
+
fastify.addSchema({
|
|
129
|
+
$id: makeSchemaUri("routeResponseInsert"),
|
|
130
|
+
type: "object",
|
|
131
|
+
// ...properties
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// in case of a POST error
|
|
135
|
+
fastify.addSchema({
|
|
136
|
+
$id: makeSchemaUri("routeResponseError"),
|
|
137
|
+
type: "object",
|
|
138
|
+
// ...properties
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
2.
|
|
142
|
+
2. Resolve schemas and use in responses
|
|
143
|
+
```js
|
|
144
|
+
const routeResponseInsert = fastify.getSchema("...");
|
|
145
|
+
const routeResponseError = fastify.getSchema("...");
|
|
146
|
+
fastify.post(
|
|
147
|
+
"/annotations/:iiifPresentationVersion/create",
|
|
148
|
+
{
|
|
149
|
+
schema: {
|
|
150
|
+
body: routeAnnotations2Or3Schema,
|
|
151
|
+
response: {
|
|
152
|
+
200: routeResponseInsert,
|
|
153
|
+
500: routeResponseError
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
async (req, rep) => {}
|
|
158
|
+
)
|
|
159
|
+
```
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Open annotations data model
|
|
2
|
+
|
|
3
|
+
[https://web.archive.org/web/20220119101135/http://www.openannotation.org/spec/core](https://web.archive.org/web/20220119101135/http://www.openannotation.org/spec/core)
|
|
4
|
+
|
|
5
|
+
[https://web.archive.org/web/20221225112722/http://www.openannotation.org/spec/core/core.html](https://web.archive.org/web/20221225112722/http://www.openannotation.org/spec/core/core.html)
|
|
6
|
+
|
|
7
|
+
[https://www.w3.org/TR/annotation-vocab/](https://www.w3.org/TR/annotation-vocab/)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
The IIIF 2.x standard relies on the W3C Open annotations (OA) standard. Established in 2013, it has since been deprecated in favour of the W3C's Web annotations model, described in the next section. To make things fun, the site is not longer available and must be accessed through the wayback machine.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## In general
|
|
15
|
+
|
|
16
|
+
- an Annotation is **a set of interconnected resources**, made mostly of a `Body` and a `Target`
|
|
17
|
+
- the data model is **implementation independant** and can be implemented in JSON, RDF-XML, Turtle...
|
|
18
|
+
- the standard extamples are in RDF, while IIIF uses JSON-ld. In turn, keys vary from the IIIF standard. When there are differences, I indicate both what is in the OA examples, and what is used in IIIF.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Vocabulary
|
|
23
|
+
|
|
24
|
+
- `Annotation`: a set of interconnected resources, with at its core a `Body` and a `Target`, where the `Body` is about the `Target`.
|
|
25
|
+
- `Target`: the object being annotated
|
|
26
|
+
- `Body`: the annotation's contents
|
|
27
|
+
- `Motivation`: the motivation further describes the relationship between `Body` and `Target`
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Namespaces
|
|
32
|
+
|
|
33
|
+
<table>
|
|
34
|
+
<tbody><tr><th>Prefix</th><th>Namespace</th><th>Description</th></tr>
|
|
35
|
+
<tr><td>oa</td><td>http://www.w3.org/ns/oa#</td> <td>The Open Annotation ontology</td></tr>
|
|
36
|
+
|
|
37
|
+
<tr><td>cnt</td><td>http://www.w3.org/2011/content#</td><td><a href="https://web.archive.org/web/20220119101135/http://www.w3.org/TR/Content-in-RDF10/">Representing Content in RDF</a></td></tr>
|
|
38
|
+
<tr><td>dc</td><td>http://purl.org/dc/elements/1.1/</td><td><a href="https://web.archive.org/web/20220119101135/http://dublincore.org/documents/dces/">Dublin Core Elements</a></td></tr>
|
|
39
|
+
<tr><td>dcterms</td><td>http://purl.org/dc/terms/</td><td><a href="https://web.archive.org/web/20220119101135/http://dublincore.org/documents/dcmi-terms/">Dublin Core Terms</a></td></tr>
|
|
40
|
+
<tr><td>dctypes</td><td>http://purl.org/dc/dcmitype/</td><td><a href="https://web.archive.org/web/20220119101135/http://dublincore.org/documents/dcmi-type-vocabulary/">Dublin Core Type Vocabulary</a></td></tr>
|
|
41
|
+
<tr><td>foaf</td><td>http://xmlns.com/foaf/0.1/</td><td><a href="https://web.archive.org/web/20220119101135/http://xmlns.com/foaf/spec/">Friend-of-a-Friend Vocabulary</a></td></tr>
|
|
42
|
+
<tr><td>prov</td><td>http://www.w3.org/ns/prov#</td><td><a href="https://web.archive.org/web/20220119101135/http://www.w3.org/TR/prov-o/">Provenance Ontology</a></td></tr>
|
|
43
|
+
<tr><td>rdf</td><td>http://www.w3.org/1999/02/22-rdf-syntax-ns#</td><td><a href="https://web.archive.org/web/20220119101135/http://www.w3.org/TR/rdf-syntax-grammar/">RDF</a></td></tr>
|
|
44
|
+
<tr><td>rdfs</td><td>http://www.w3.org/2000/01/rdf-schema#</td><td><a href="https://web.archive.org/web/20220119101135/http://www.w3.org/TR/rdf-schema/">RDF Schema</a></td></tr>
|
|
45
|
+
<tr><td>skos</td><td>http://www.w3.org/2004/02/skos/core#</td><td><a href="https://web.archive.org/web/20220119101135/http://www.w3.org/TR/skos-reference/">Simple Knowledge Organization System</a></td></tr>
|
|
46
|
+
<tr><td>trig</td><td>http://www.w3.org/2004/03/trix/rdfg-1/</td><td><a href="https://web.archive.org/web/20220119101135/http://www.w3.org/2004/03/trix/">TriG Named Graphs</a></td></tr>
|
|
47
|
+
|
|
48
|
+
</tbody></table>
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Annotations
|
|
53
|
+
|
|
54
|
+
An annotation has 1+ `Targets` and 0+ `Bodies`
|
|
55
|
+
- IIIF `@id`: `URI`
|
|
56
|
+
- is used to identify with an URI the `Annotation`, `Body` and `Target`
|
|
57
|
+
- in Turtle, there is no specific key because the standard is different.
|
|
58
|
+
- `a` (IIIF `@type`): `oa:Annotation`
|
|
59
|
+
- this indicates that the resource is an `Annotation`
|
|
60
|
+
- `oa:hasBody` (IIIF `resource`): `Body | Body[]`
|
|
61
|
+
- the relationship between `Annotation` and `Body`
|
|
62
|
+
- the body MAY be omitted if there is no content to annotate
|
|
63
|
+
- `oa:hasTarget` (IIIF `on`): `Target | Target[]`
|
|
64
|
+
- the relationship between `Annotation` and `Target`
|
|
65
|
+
- `oa:Motivation` (IIIF `motivation`): `string[]` (in IIIF, `oa:commenting | sc:painting` are frequent)
|
|
66
|
+
- the motivation, or role of the annotation
|
|
67
|
+
- `all allowed values are: `oa:bookmarking | oa:classifying | oa:commenting | oa:describing | oa:editing | oa:highlighting | oa:identifying | oa:linking | oa:moderating | oa:questionning | oa:replying | oa:tagging`
|
|
68
|
+
- in IIIF, `sc:painting` is also allowed to indicate a painting annotation. The most useful values are `sc:painting | oa:commenting`.
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
// basic structure
|
|
72
|
+
{
|
|
73
|
+
"@id": "<URI>",
|
|
74
|
+
"@type": "oa:Annotation",
|
|
75
|
+
"motivation": "sc:painting" || "oa:commenting", // or an array of values
|
|
76
|
+
"resource": {
|
|
77
|
+
"@id": "<URI?>",
|
|
78
|
+
"@type": "<dctype>",
|
|
79
|
+
// other keys
|
|
80
|
+
},
|
|
81
|
+
"on": "<URI>" || SpecificResource // `on` can be either specified as the URI to a canvas, or as a SpecificResource
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
### Bodies and targets
|
|
85
|
+
|
|
86
|
+
- `a` (IIIF `@type`): `dctypes:Dataset | dctypes:Image | dctypes:MovingImage | dctypes:Sound | dcTypes:Text`
|
|
87
|
+
- SHOULD be used to describe the type of a `Body` or `Target`
|
|
88
|
+
- IIIF `@id`: `URI`
|
|
89
|
+
|
|
90
|
+
Special cases:
|
|
91
|
+
- an annotation may have **no `Body`**
|
|
92
|
+
- an annotation may have **multiple `Bodies` or `Targets`**.
|
|
93
|
+
- in that case, **each body is related to each target individually**. If we have 2 bodies and 2 targets, we have the following situation:
|
|
94
|
+
```
|
|
95
|
+
body1 <-> target1
|
|
96
|
+
body1 <-> target2
|
|
97
|
+
body2 <-> target1
|
|
98
|
+
body2 <-> target2
|
|
99
|
+
```
|
|
100
|
+
- you may have 1 `Target` and N `Bodies` (several annotations apply to a single body), or N `Target` and 1 `Bodies` (1 annotation applies to several images) or an N to N relationship.
|
|
101
|
+
|
|
102
|
+
### Embedded textual body (ETB)
|
|
103
|
+
|
|
104
|
+
ETBs are used to embed textual content directly in an annotation instead of referencing it by an `id`.
|
|
105
|
+
- `a` (IIIF `@type`): `cnt:ContentAsText | dctypes:Text`
|
|
106
|
+
- MUST be used to indicate that this is an ETB. MAY contain both values described above.
|
|
107
|
+
- `cnt:chars` (IIIF `chars`): `string`
|
|
108
|
+
- MUST Be used to contain the contents of the ETB.
|
|
109
|
+
- `dc:format` (SAS `format`): `mimetype`
|
|
110
|
+
- SHOULD be used to describe the mimetype of the ETB
|
|
111
|
+
- for example, it can distinguish plain text form HTML.
|
|
112
|
+
- `dc:language`: `string`
|
|
113
|
+
- the language the ETB is in
|
|
114
|
+
- language codes SHOULD follow [RFC 3066](https://www.ietf.org/rfc/rfc3066.txt)
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
{
|
|
118
|
+
"@type" : "dctypes:Text",
|
|
119
|
+
"format" : "text/html",
|
|
120
|
+
"chars" : "<p>This is en embedded textual content from SAS</p>",
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Tags
|
|
125
|
+
|
|
126
|
+
A tag is a specific type of `Body`: a keyword or label used to annotate a `Target`. There are 2 types of tags: "normal" and "semantic" tags
|
|
127
|
+
- `a` (IIIF `@type`): `oa:Tag | oa:SemanticTag`
|
|
128
|
+
- `oa:Tag` describes a non-semantic tag. In that case, the tag functions like an ETB
|
|
129
|
+
- `oa:SemanticTag` describes a semantic tag. Instead of containing the contents of the tag, the `Body` references an external URI.
|
|
130
|
+
- `oa:Motivation` (IIIF `motivation`): `oa:tagging`
|
|
131
|
+
- `oa:tagging` should be added to the `oa:Motivation` of the annotation to indicate it's a tag
|
|
132
|
+
|
|
133
|
+
### Fragment URIs
|
|
134
|
+
|
|
135
|
+
Fragment URIs can be used to target part of a resource.
|
|
136
|
+
- fragments ARE NOT COMPATIBLE with `SpecificResources`. Either use one or the other.
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
{
|
|
140
|
+
"@id": "http://example.com/image.jpg#xywh=1,1,1,1"
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Specific Resources
|
|
147
|
+
|
|
148
|
+
[https://web.archive.org/web/20221225112722/http://www.openannotation.org/spec/core/specific.html](https://web.archive.org/web/20221225112722/http://www.openannotation.org/spec/core/specific.html)
|
|
149
|
+
|
|
150
|
+
`SpecificResources` are an extension of the core data model to reference part of a resource with more precision than Fragment URIs. They can be used either in `Bodies` or `Targets` (though in IIIF they are used mostly in `Targets`).
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
// basic IIIF structure for a SpecificResource
|
|
154
|
+
{
|
|
155
|
+
"@id": "<annotationId>",
|
|
156
|
+
"@type": "oa:SpecificResource",
|
|
157
|
+
"full": {
|
|
158
|
+
// link to the full image
|
|
159
|
+
},
|
|
160
|
+
"selector": {
|
|
161
|
+
"@type": "<selectorType>",
|
|
162
|
+
"value": "<selectorValue>"
|
|
163
|
+
},
|
|
164
|
+
"within": {
|
|
165
|
+
// extra context, such as the IIIF manifest in which to include the annotation
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Basic units
|
|
171
|
+
|
|
172
|
+
3 conceptual units are present in the Specific Resource model:
|
|
173
|
+
- `Source resource`: the complete target , i.e. an entire image that we want to select a section of.
|
|
174
|
+
- `SpecificResource`: the segment of the resource described by the `Specifier`
|
|
175
|
+
- `Specifiers`: describes how to determine the aspects of the `Source` that constitute the Specific Resource.
|
|
176
|
+
|
|
177
|
+
### Specifiers
|
|
178
|
+
|
|
179
|
+
There are 3 types of specifiers:
|
|
180
|
+
- `oa:State`: specifies the state of a `Body` or a `Target` applicable to an `Annotation` in order to retrieve the proper representation of the resource. For example,
|
|
181
|
+
- the timestamp at which a `Body` was created (if there are several versions of the same resource with different timestamps)
|
|
182
|
+
- `oa:Selector`: specifies the segment of a resource relevant to the `Annotation`: text range, image section, SVG selector...
|
|
183
|
+
- `oa:Style`: a description of how to style an `Annotation`
|
|
184
|
+
|
|
185
|
+
If `State` and `Selector` are present in a annotation, the `State` will be processed before `Selector` to be sure that we have the proper state.
|
|
186
|
+
|
|
187
|
+
Keys:
|
|
188
|
+
- `a` (IIIF `@type`) `oa:SpecificResource`: the specific resource
|
|
189
|
+
- `oa:hasSource` (SAS `full`): the `Source Resource` being specified by the `Specific Resource`.
|
|
190
|
+
- MUST be used: the `Specific Resource` is defined relatively to the `Source Resource`, and so the renderer will fetch the `Source` before targeting the `Specific resource` part.
|
|
191
|
+
|
|
192
|
+
I'll only present selectors, not `states` or `styles` that aren't used in IIIF.
|
|
193
|
+
|
|
194
|
+
### Selectors
|
|
195
|
+
|
|
196
|
+
A `Selector` is a `Specifier` which describes how to determine the segment of interest from within the retrieved representation of the Source resource. There MUST be only 1 `Selector` per specifier, otherwise use the [multiplicity module](https://web.archive.org/web/20221225112718/http://www.openannotation.org/spec/core/multiplicity.html).
|
|
197
|
+
|
|
198
|
+
- `oa:hasSource` (IIIF `full`): the `Source resource`
|
|
199
|
+
- `oa:hasSelector` (IIIF `selector`): the `Selector`'s content
|
|
200
|
+
|
|
201
|
+
There are selectors other than those presented below: `RangeSelector`, `TextPositionSelector`, `TextQuoteSelector`... but they are out of scope with IIIF so I won't talk about them
|
|
202
|
+
|
|
203
|
+
#### FragmentSelector
|
|
204
|
+
|
|
205
|
+
- `a` (IIIF `@type`): `oa:FragmentSelector`
|
|
206
|
+
- `rdf:value` (IIIF `value`): `string'
|
|
207
|
+
- `dcterms:conformsTo`: the specification that defines the syntax of the fragment.
|
|
208
|
+
- for IIIF, targets can only be targeted using IIIF-specific `ImageApiSelector` or W3C media fragments (`xhwh=int,int,int,int`).
|
|
209
|
+
- SHOULD be used
|
|
210
|
+
- SHOULD be one of:
|
|
211
|
+
|
|
212
|
+
<table>
|
|
213
|
+
<tbody><tr><th>Fragment Specification</th><th>Description</th></tr>
|
|
214
|
+
<tr><td>http://tools.ietf.org/rfc/rfc3236</td><td><a href="https://web.archive.org/web/20221225112722/http://tools.ietf.org/rfc/rfc3236">XHTML, and HTML</a>. Example: #namedSection </td></tr>
|
|
215
|
+
<tr><td>http://tools.ietf.org/rfc/rfc3778</td><td><a href="https://web.archive.org/web/20221225112722/http://tools.ietf.org/rfc/rfc3778">PDF</a>. Example: #page=10&viewrect=50,50,640,480</td></tr>
|
|
216
|
+
<tr><td>http://tools.ietf.org/rfc/rfc5147</td><td><a href="https://web.archive.org/web/20221225112722/http://tools.ietf.org/rfc/rfc5147">Plain Text</a>. Example: #char=0,10</td></tr>
|
|
217
|
+
<tr><td>http://tools.ietf.org/rfc/rfc3023</td><td><a href="https://web.archive.org/web/20221225112722/http://tools.ietf.org/rfc/rfc3023">XML</a>. Example: #xpointer(/a/b/c) </td></tr>
|
|
218
|
+
<tr><td>http://www.ietf.org/rfc/rfc3870</td><td><a href="https://web.archive.org/web/20221225112722/http://www.ietf.org/rfc/rfc3870">RDF/XML</a>. Example: #namedResource </td></tr>
|
|
219
|
+
<tr><td>http://www.w3.org/TR/media-frags/</td><td><a href="https://web.archive.org/web/20221225112722/http://www.w3.org/TR/media-frags/">W3C Media Fragments</a>. Example: #xywh=50,50,640,480</td></tr>
|
|
220
|
+
<tr><td>http://www.w3.org/TR/SVG/</td><td><a href="https://web.archive.org/web/20221225112722/http://www.w3.org/TR/SVG/">SVG</a>. Example: #svgView(viewBox(50,50,640,480))</td></tr>
|
|
221
|
+
</tbody></table>
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
{
|
|
225
|
+
// ...
|
|
226
|
+
selector: {
|
|
227
|
+
"@type": "oa:FragmentSelector",
|
|
228
|
+
"value": "xywh=int,int,int,int" // so here we use the W3C media fragments selector
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### SVG selectors
|
|
235
|
+
|
|
236
|
+
`SvgSelectors` define an area through an SVG.
|
|
237
|
+
- the `SvgSelector`'s value MUST be a valid and complete SVG document
|
|
238
|
+
- the SVG SHOULD be a single shape: `path|rect|circle|ellipse|polyline|polygone|g`
|
|
239
|
+
- dimensions in the SVG MUST be relative to the `Source resource`.
|
|
240
|
+
- For example, given an image which is 600 pixels by 400 pixels, and the desired section is a circle of 100 pixel radius at the center of the image, then the SVG element would be: `<circle cx="300" cy="200" r="100"/>`
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
### Examples
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
`Specific resource` structure in IIIF 2.x.
|
|
247
|
+
```js
|
|
248
|
+
{
|
|
249
|
+
"@type" : "oa:SpecificResource",
|
|
250
|
+
"within" : {
|
|
251
|
+
"@id" : "https://aikon.enpc.fr/aikon/iiif/v2/wit9_man11_anno165/manifest.json",
|
|
252
|
+
"@type" : "sc:Manifest"
|
|
253
|
+
},
|
|
254
|
+
"selector" : {
|
|
255
|
+
"@type" : "oa:FragmentSelector",
|
|
256
|
+
"value" : "xywh=0,31,1865,1670"
|
|
257
|
+
},
|
|
258
|
+
"full" : "https://aikon.enpc.fr/aikon/iiif/v2/wit9_man11_anno165/canvas/c16.json"
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`Specific resource` that uses the IIIF `ImageApiSelector` extension
|
|
263
|
+
```js
|
|
264
|
+
{
|
|
265
|
+
// `@id` uses the IIIF Image api to describe the fragment
|
|
266
|
+
"@id": "http://www.example.org/iiif/book1-page1/50,50,1250,1850/full/0/default.jpg",
|
|
267
|
+
"@type": "oa:SpecificResource",
|
|
268
|
+
|
|
269
|
+
// `full` describes the full image.
|
|
270
|
+
"full": {
|
|
271
|
+
"@id": "http://example.org/iiif/book1-page1/full/full/0/default.jpg",
|
|
272
|
+
"@type": "dctypes:Image",
|
|
273
|
+
"service": {
|
|
274
|
+
"@context": "http://iiif.io/api/image/2/context.json",
|
|
275
|
+
"@id": "http://example.org/iiif/book1-page1",
|
|
276
|
+
"profile": "http://iiif.io/api/image/2/level2.json"
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// `selector` describes the region targeted in `@id`
|
|
281
|
+
"selector": {
|
|
282
|
+
"@context": "http://iiif.io/api/annex/openannotation/context.json",
|
|
283
|
+
"@type": "iiif:ImageApiSelector",
|
|
284
|
+
"region": "50,50,1250,1850"
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Multiplicity
|
|
292
|
+
|
|
293
|
+
[https://web.archive.org/web/20221225112718/http://www.openannotation.org/spec/core/multiplicity.html](https://web.archive.org/web/20221225112718/http://www.openannotation.org/spec/core/multiplicity.html)
|
|
294
|
+
|
|
295
|
+
In IIIF, this module is useful if we want to specify more than 1 `SpecificResource`. In OA, the multiplicity module is used to have multiple bodies or targets in an annotation (IE: 3 bodies in 3 languages). Options for multiplicity are:
|
|
296
|
+
|
|
297
|
+
- `oa:Choice`: only one of several bodies/targets will be rendered at once. Done by defining 1 default value and other alternative values
|
|
298
|
+
- `oa:Composite`: when all resources are required for an annotation to function properly.
|
|
299
|
+
- `oa:List`: like a composite, but ordered
|
|
300
|
+
|
|
301
|
+
`Bodies` and `Targets` can use `multiplicity` by setting their `@type` to `oa:Choice | oa:Composite |oa:List`.
|
|
302
|
+
|
|
303
|
+
I have only ever seen `oa:Choice` be used in IIIF so that's the one I'll present.
|
|
304
|
+
|
|
305
|
+
### `oa:Choice`
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
{
|
|
309
|
+
"@type": "oa:Choice",
|
|
310
|
+
"default": /** default choice */,
|
|
311
|
+
"item": /** one or several other available items */
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Example: a choice between a `FragmentSelector` and an `SvgSelector`
|
|
316
|
+
```js
|
|
317
|
+
{
|
|
318
|
+
"@type" : "oa:Choice",
|
|
319
|
+
"default" : {
|
|
320
|
+
"@type" : "oa:FragmentSelector",
|
|
321
|
+
"value" : "xywh=0,31,1865,1670"
|
|
322
|
+
},
|
|
323
|
+
"item" : {
|
|
324
|
+
"@type" : "oa:SvgSelector",
|
|
325
|
+
"value" : "<svg xmlns=\"http://www.w3.org/2000/svg\"><path xmlns='http://www.w3.org/2000/svg' d='M0 31 h 932 v 0 h 932 v 835 v 835 h -932 h -932 v -835Z' id='rectangle_wit9_man11_anno165_c16_5ebd8bc8044b45b58aae2a508ca75eed' data-paper-data='{"strokeWidth":1,"rotation":0,"annotation":null,"nonHoverStrokeColor":["Color",0,1,0],"editable":true,"deleteIcon":null,"rotationIcon":null,"group":null}' fill-opacity='0' fill='#00ff00' fill-rule='nonzero' stroke='#00ff00' stroke-width='1' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='10' stroke-dashoffset='0' style='mix-blend-mode: normal'/></svg>"
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|