#
IIIF
The IIIF plugin provides functionality relating to the IIIF data standard. In particular, it provides a configurable endpoint for Manifests and other Resources defined in the IIIF Presentation API (v3.0.0).
#
REST Endpoints
#
GET /iiif/3/manifest/{manifest_key}/{*path}
Generates and retrieves the IIIF Manifest or embedded Resource for the record identified by the provided manifest key.
The following steps are performed when a request is handled by this endpoint:
A GetByIdentifierRequest is executed against the data service using:
- The
manifest_key
path parameter as theidentifier
field - The value of the configuration property,
rosetta.plugins.iiif.identifier_type
, as theidentifier_type
field (if specified) - The value of the configuration property,
rosetta.plugins.iiif.profile
, as the root profile for the data service request
Note that this implies a request transform should be configured in the Profile that transforms a
GetByIdentifierRequest
into a form accepted by the configured Provider(s) (e.g. an Elasticsearch request). SeeConverting the Request for more details.- The
Assuming a single result is found, it is expected to have been transformed into a data structure that conforms to the
SparseManifest schema using a Glyph configured in the data phase of the Profile, for instance. SeeTemplating a Manifest for further details.- If no results or multiple results are found, then this endpoint responds with a 404 error.
The
SparseManifest
is "expanded" to a full IIIF Manifest, where ids for embedded resources are automatically generated, if absent.(Optional) Apply a "late" transform against the full IIIF Manifest, allowing it to be populated by custom logic.
- The input for this transform is a Metadata object whose
data
field is the full Manifest and whoseid
andsource
fields are their respective fields of the retrieved result.
- The input for this transform is a Metadata object whose
If the
path
path parameter is provided, navigate to the embedded Resource located at this path within the full IIIF Manifest.Check that the final output is valid against the IIIF Presentation specification:
- If not, respond with a 404 error.
- Otherwise, respond with the IIIF Resource.
See
#
Request
Parameters
#
Response
Body
Schema: IIIF Presentation API (v3.0.0) Resource
Error
404: If no unique record exists for the provided manifest key or the path (if provided) does not navigate to a valid IIIF Resource.
#
Converting the Request
Upon receiving a request to the
This can be done by first declaring a Glyph (such as a Proteus Glyph)
in the application.yml
:
application.yml
rosetta:
transform:
glyphs:
- name: request-transform
type: proteus
properties:
spec: /rosetta/config/proteus/request-transform.spec
where the above Glyph depends on the spec file, /rosetta/config/proteus/request-transform.spec
.
Let us assume our Provider is for an Elasticsearch index named my-index
in a cluster running locally on port 9200:
application.yml
rosetta:
provider:
providers:
- name: my-provider
type: elasticsearch
properties:
protocol: http
host: localhost
port: 9200
index: my-index
and the mappings for my-index
include a keyword field, system_id
, which contains a unique identifier string
for each record in the index.
If a request is received by the IIIF manifest endpoint with path /iiif/3/manifest/object-123
, then the following
GetByIdentifierRequest
will be submitted to the data service
(provided the configuration property rosetta.plugins.iiif.identifier_type
is left unspecified):
{
"identifier": "object-123"
}
It follows that the Proteus spec for the request transform should be:
/rosetta/config/proteus/request-transform.spec
{
"query": {
"term": {
"system_id": "$.identifier"
}
}
}
In the above, we have constructed a term query
against the system_id
field using the manifest key from the request as the term value and sole template parameter,
de-referenced using the JSONPath string $.identifier
.
Finally, we must register the request transform within the request phase of the Profile used by the IIIF plugin
(see
application.yml
rosetta:
profile:
profiles:
# Create profile for IIIF:
- name: iiif
providers:
policy: list
names:
- my-provider # Our Elasticsearch provider configured earlier
transforms:
request:
- glyphs:
policy: list
names:
- request-transform # <--- Reference request transform Glyph here
criteria: # Not strictly necessary with only one provider, but it may be with multiple.
providers:
policy: list
names:
- my-provider
plugins:
iiif:
profile: iiif # <-- Specify that we should use the above profile for /iiif/3/manifest requests
# (Other IIIF config)
Now Rosetta knows to transform the incoming GetByIdentifierRequest
into an Elasticsearch search request using
the Glyph named request-transform
when serving IIIF requests.
#
Templating a Manifest
#
Expected Format
In this plugin, IIIF Manifests must be templated in the form of a
The following is an example of a SparseManifest
that includes the label
, metadata
and items
fields:
{
"type": "manifest",
"id": "object-123",
"label": "Impression, Sunrise",
"metadata": {
"Creator": "Claude Monet",
"Genre": "Marine Art",
"Depicts": [
"Sunrise",
"Port of Le Havre",
"Barque"
],
"Title": {
"en": "Impression, Sunrise",
"fr": "Impression, soleil levant"
}
},
"items": [
{
"type": "canvas",
"artifact": {
"location": "wikipedia/commons/thumb/5/59/Monet_-_Impression,_Sunrise.jpg/960px-Monet_-_Impression,_Sunrise.jpg"
}
}
]
}
Note that entries in the metadata
map are allowed to be specified either as a simple string, an array of strings,
or a map whose keys are language codes. This allows manifests to be populated as concisely or as richly
as is needed. The metadata
field may also be populated "literally" as an array of label-value maps,
as in the IIIF specification.
Once specified, a SparseManifest
is expanded into a full IIIF manifest on retrieval from the /iiif/3/manifest
endpoint.
Expanding the above SparseManifest
may result in the following output
(depending on the
{
"@context": "http://iiif.io/api/presentation/3/context.json",
"id": "http://localhost:4923/iiif/3/manifest/object-123",
"type": "Manifest",
"label": {
"en": [
"Impression, Sunrise"
]
},
"metadata": [
{
"label": {
"en": [
"Creator"
]
},
"value": {
"en": [
"Claude Monet"
]
}
},
{
"label": {
"en": [
"Genre"
]
},
"value": {
"en": [
"Marine Art"
]
}
},
{
"label": {
"en": [
"Depicts"
]
},
"value": {
"en": [
"Sunrise",
"Port of Le Havre",
"Barque"
]
}
},
{
"label": {
"en": [
"Title"
]
},
"value": {
"en": [
"Impression, Sunrise"
],
"fr": [
"Impression, soleil levant"
]
}
}
],
"items": [
{
"id": "http://localhost:4923/iiif/3/manifest/object-123/items/0",
"type": "Canvas",
"items": [
{
"id": "http://localhost:4923/iiif/3/manifest/object-123/items/0/items/0",
"type": "AnnotationPage",
"items": [
{
"id": "http://localhost:4923/iiif/3/manifest/object-123/items/0/items/0/items/0",
"type": "Annotation",
"body": {
"id": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/Monet_-_Impression,_Sunrise.jpg/960px-Monet_-_Impression,_Sunrise.jpg",
"type": "Image"
},
"motivation": "painting",
"target": "http://localhost:4923/iiif/3/manifest/object-123/items/0"
}
]
}
]
}
]
}
As can be seen above, many fields have been added or altered:
- The field
@context
field has been added with the appropriate value for version 3 of the IIIF Presentation API. - The
id
field is now fully qualified against the base retrieval URL. - The
label
andmetadata
values have been used to generate full language maps using the pre-configured default language code. - Each item in the
items
array (just one in this example) has been exploded into the multiply-nested series of IIIF Resources including aCanvas
,AnnotationPage
,Annotation
and anImage
, each of which are required to display the referenced image. - Media artifact locations have been resolved against the base media url (in this case
https://upload.wikimedia.org
), which is configured using the property,rosetta.plugins.iiif.external_media_base_url
. This URL is used to populate theid
field ofImage
Resources. - Each structural Resource embedded in the root manifest has been automatically given an
id
that allows the Resource to be retrieved in isolation using the/iiif/3/manifest
endpoint.- Note: ids are not generated for Resources whose
id
is already specified in the template.
- Note: ids are not generated for Resources whose
See
#
Transforming to a SparseManifest
In general, the records retrieved by a Provider
will not already be in the SparseManifest
format, so they must be
transformed such that they are. To do this, at least one Glyph
must be declared (such as a Proteus Glyph)
and then used in the data phase of the profile.
The following example demonstrates how this might be done. First, let us assume our Provider produces results that have the following form:
{
"system_id": "object-123",
"title": "Impression, Sunrise",
"title_french": "Impression, soleil levant",
"creator": "Claude Monet",
"genre": "Marine Art",
"subjects": [
"Sunrise",
"Port of Le Havre",
"Barque"
],
"media": [
{
"uri": "wikipedia/commons/thumb/5/59/Monet_-_Impression,_Sunrise.jpg/960px-Monet_-_Impression,_Sunrise.jpg"
}
]
}
We will define a Proteus Glyph in the application.yml
file with the following properties:
application.yml
rosetta:
transform:
glyphs:
- name: iiif-template
type: proteus
properties:
spec: /rosetta/config/proteus/iiif-template.json
The Glyph depends on a spec
file, /rosetta/config/proteus/iiif-template.json
, whose content is as follows:
/rosetta/config/proteus/iiif-template.json
{
"type": "manifest",
"id": "$.system_id",
"label": "$.title",
"metadata": {
"Creator": "$.creator",
"Genre": "$.genre",
"Depicts": "$.subjects",
"Title": {
"en": "$.title",
"fr": "$.title_french"
}
},
"items": {
"#type": "for_each",
"values": "$.media",
"spec": {
"type": "canvas",
"artifact": {
"location": "$.uri"
}
}
}
}
The above configuration will reproduce the example SparseManifest
from the previous section.
#
Using the Transform
Finally, we need to wire our formatter/templating Glyph into the IIIF Manifest endpoint. We do this by first declaring a Profile that uses the Glyph in the data transformation phase, as follows:
application.yml
rosetta:
profile:
profiles:
- name: iiif
providers:
policy: list
names:
- my-provider # A provider with this name must be declared
transforms:
request: # A request transform may be necessary, depending on the provider.
data:
- glyphs:
policy: list
names:
- iiif-template # <--- Refers to our example Glyph
Then, specify that the IIIF endpoint should use this profile in the plugin configuration properties:
rosetta:
plugins:
iiif:
profile: iiif # <--- Specify profile here
# Also specify the necessary base urls:
base_url: http://localhost:4923
external_media_base_url: https://upload.wikimedia.org
Now, requests to http://localhost:4923/iiif/3/manifest/{manifest_key}
will respond with full IIIF manifests.
#
Configuration Properties
Each of the following properties is prefixed by the property path rosetta.plugins.iiif
.
#
Data Structures
#
SparseManifest
A sparse representation of a IIIF Manifest. In the following schema, all fields correspond to the property of the same name as described in the IIIF Presentation API specification unless otherwise stated.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "Generates a IIIF Manifest Resource",
"$defs": {
"SparseResource": {
"type": "object",
"properties": {
"type": {
"description": "The type of SparseResource. Determines the type of IIIF Resource it generates and what additional properties are available.",
"enum": [
"manifest",
"canvas",
"range",
"image"
]
},
"id": {
"type": "string",
"description": "The id for this SparseResource. Leave unspecified to use path-based auto-generation. For the root SparseManifest, this must be set to the value of the 'manifest_key' path parameter used to retrieve this manifest."
},
"label": {
"$ref": "#/$defs/AdaptiveStringMap"
},
"summary": {
"$ref": "#/$defs/AdaptiveStringMap"
},
"metadata": {
"$ref": "#/$defs/AdaptiveMetadata"
},
"thumbnail": {
"type": "array",
"items": {
"$ref": "#/$defs/SparseImage"
}
}
},
"required": [
"type",
"id"
]
},
"AdaptiveStringMap": {
"oneOf": [
{
"$ref": "#/$defs/StringValueNode",
"description": "If this is a value node (i.e. not an object or array), then on expansion, the resulting language map will have one entry whose key is given by the default language code and whose value will be this value, converted to a string if necessary, and wrapped in an array."
},
{
"type": "array",
"items": {
"$ref": "#/$defs/StringValueNode"
},
"description": "If this is an array, then on expansion, the resulting language map will have one entry whose key is given by the default language map, and the value will be this array where each item has been converted to a string, if necessary."
},
{
"type": "object",
"description": "If this is an object, then on expansion, the resulting language map will have the same keys as this map, interpreted as language codes, and each value may either by a value node or an array, where value nodes are converted to strings and wrapped (as above), and arrays are used as-is after each item has been converted to a string as necessary.",
"additionalProperties": {
"oneOf": [
{
"$ref": "#/$defs/StringValueNode"
},
{
"type": "array",
"items": {
"$ref": "#/$defs/StringValueNode"
}
}
]
}
}
]
},
"AdaptiveMetadata": {
"oneOf": [
{
"type": "object",
"description": "If this is an object, then on expansion, each entry will contribute one item to the resulting 'metadata' array, where the 'label' language map will have one entry whose key is the default language code, and whose value is the key of the map entry, wrapped in an array. The 'value' language map will be the value of the map entry interpreted as an 'AdaptiveStringMap'.",
"additionalProperties": {
"$ref": "#/$defs/AdaptiveStringMap"
}
},
{
"type": "array",
"description": "If this is an array, then on expansion, the resulting 'metadata' array will contain one item for each item in this array, where the 'label' and 'value' fields of each item are each interpreted as an 'AdaptiveStringMap'.",
"items": {
"type": "object",
"properties": {
"label": {
"$ref": "#/$defs/AdaptiveStringMap"
},
"value": {
"$ref": "#/$defs/AdaptiveStringMap"
}
}
}
}
]
},
"SparseImage": {
"description": "Generates a IIIF Image Resource",
"allOf": [
{
"$ref": "#/$defs/SparseResource"
},
{
"type": "object",
"properties": {
"type": {
"const": "image"
},
"format": {
"type": "string",
"description": "The media/MIME type of the image"
},
"height": {
"type": "integer",
"description": "The height of the image in pixels"
},
"width": {
"type": "integer",
"description": "The width of the image in pixels"
}
}
}
]
},
"SparseCanvas": {
"description": "Generates a IIIF Canvas Resource",
"allOf": [
{
"$ref": "#/$defs/SparseResource"
},
{
"type": "object",
"properties": {
"type": {
"const": "canvas"
},
"artifact": {
"$ref": "#/$defs/SparseArtifact",
"description": "A media artifact object that contains all necessary information required to generate a fully populated AnnotationPage for this Canvas"
}
}
}
]
},
"SparseRange": {
"description": "Generates a IIIF Range Resource",
"allOf": [
{
"$ref": "#/$defs/SparseResource"
},
{
"type": "object",
"properties": {
"type": {
"const": "range"
},
"items": {
"type": "array",
"items": {
"$ref": "#/$defs/SparseResource"
}
}
}
}
]
},
"SparseArtifact": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the media artifact. If set to 'zoom', then the IIIF Image service will be used."
},
"location": {
"type": "string",
"description": "The relative path of the media artifact. This value will be resolved against one of the configured media base urls upon expansion, depending on whether the IIIF Image service is used."
},
"format": {
"type": "string",
"description": "The media/MIME type of the image"
},
"height": {
"type": "integer",
"description": "The height of the image in pixels"
},
"width": {
"type": "integer",
"description": "The width of the image in pixels"
},
"file_size": {
"type": "integer",
"description": "The size of the media artifact in bytes"
},
"use_service": {
"type": "boolean",
"description": "If true, then the IIIF Image service will be used."
},
"info_path": {
"type": "string",
"description": "The path template for the id of the IIIF Service Resource, if the IIIF Image service is used. The placeholder '{location}' will be replaced with the 'location' value of this artifact.",
"default": "/{location}/info.json"
},
"request_path": {
"type": "string",
"description": "The path template for the id of the Image resource, if the IIIF Image service is used. The placeholder '{location}' will be replaced with the 'location' value of this artifact.",
"default": "/{location}/full/max/0/default.jpg"
}
},
"required": [
"location"
]
},
"StringValueNode": {
"description": "A value node (i.e. not an object or array) that is converted to a string (if it is not one already) upon expansion into a language map.",
"not": {
"anyOf": [
{
"type": "object"
},
{
"type": "array"
}
]
}
}
},
"allOf": [
{
"$ref": "#/$defs/SparseResource"
},
{
"type": "object",
"properties": {
"type": {
"const": "manifest"
},
"items": {
"type": "array",
"items": {
"$ref": "#/$defs/SparseCanvas"
}
},
"structures": {
"type": "array",
"items": {
"$ref": "#/$defs/SparseRange"
}
}
}
}
]
}