Skip to content

dainst/iiif_image_plug

Repository files navigation

CI status

IIIF Image Plug

An Elixir plug implementing the International Image Interoperability Framework (IIIF) image API specification.

  • The goal of IIIF is to define a standardised REST API for serving high resolution images (art, photographes or archival material published by museums, universities and similar institutions).
  • This plug library needs you to define a mapping between image identifier (used in the REST API) and file system path, and will then do the image transformations based on the other request parameters for you.
  • There exist several generic Javascript IIIF viewers that utilize this API to allow for optimized viewing (dynamic loading of parts of the image data based on zoom level/viewport).
  • WebGIS Javascript libraries like leaflet or OpenLayers support IIIF in one way or the other.
  • For the time beeing only (the current) Image API 3.0 is implemented, check out the IIIF documentation for its capabilities.
  • The image processing is handled by libvips via Vix.
  • The IIIF image API implemented by this library is just one (the foundational) of currently six API standards the IIIF community defines for serving multimedia content and metadata.

Installation

The package can be installed by adding iiif_image_plug to your list of dependencies in mix.exs:

def deps do
  [
    {:iiif_image_plug, "~> 0.6.1"}
  ]
end

Usage

Assuming you want to serve IIIF in your plug based server at "/iiif/v3", add a forward route like this:

  forward("/iiif/v3",
    to: MyApp.IIIFPlug,
    init_opts: %IIIFImagePlug.V3.Options{}
  )

For Phoenix it would look slightly different:

  forward("/iiif/v3", MyApp.IIIFPlug, %IIIFImagePlug.V3.Options{})

Example plug

A plug implementation may look something like this:

defmodule MyApp.IIIFPlug do
  use IIIFImagePlug.V3

  # There are two required callbacks you have to implement, plus 
  # several optional ones. See the `IIIFImagePlug.V3` 
  # documentation for more.

  @impl true
  def info_request(identifier) do
    # The first required callback lets you inject some metadata 
    # from your application into the plug when it is responding to
    # an information request (info.json) for a specific `identifier`. 
    # The only required field is `:path`, which tells the plug the 
    # file system path matching the given `identifier`.

    MyApp.ContextModule.get_image_metadata(identifier)
    |> case do
      %{path: path, rights_statement: rights} ->
        {
          :ok,
          %IIIFImagePlug.V3.InfoRequest{
            path: path,
            rights: rights
          }
        }
      {:error, :not_found} ->
        {
          :error,
          %IIIFImagePlug.V3.RequestError{
            status_code: 404,
            msg: :not_found
          }
        }
    end
  end

  @impl true
  def data_request(identifier) do
    # The second required callback lets you inject some metadata 
    # from your application into the plug when it is responding to
    # an actual image data request for a specific `identifier`. As 
    # with `info_request/1`, the only required field is `:path`, which 
    # tells the plug the file system path matching the given `identifier`.
    MyApp.ContextModule.get_image_path(identifier)
    |> case do
      {:ok, path} ->
        {
          :ok,
          %IIIFImagePlug.V3.DataRequest{
            path: path,      
            response_headers: [
              {"cache-control", "public, max-age=31536000, immutable"}
            ]
          }
        }
      {:error, :not_found} ->
        {
          :error,
          %IIIFImagePlug.V3.RequestError{
            status_code: 404,
            msg: :not_found
          }
        }
    end
  end

CORS

For your service to fully implement the API specification, you need to properly configure Cross-Origin Resource Sharing (CORS). You could either set the correct headers in your info_request/1 or data_request/1 implementation or configure the appropriate headers in a plug before this one (cors_plug was used in this example):

(..)
  plug(CORSPlug, origin: ["*"])
  plug(:match)
  plug(:dispatch)

  forward("/",
    to: MyApp.IIIFPlug,
    init_opts: (..)
  )
end
(..)

Testing your endpoint

Because this plug is just a library and only part of your overall application, you might want to test your service's IIIF compliance against the official validator:

Development

This repository comes with a minimalistic server, run the server with:

iex -S mix run

The metadata of the main sample file test/images/bentheim.jpg can now be accessed at http://localhost:4000/bentheim.jpg/info.json:

{
    "id": "http://localhost:4000/bentheim.jpg",
    "profile": "level2",
    "type": "ImageServer3",
    "protocol": "http://iiif.io/api/image",
    "rights": "https://creativecommons.org/publicdomain/zero/1.0/",
    "width": 3000,
    "height": 2279,
    "@context": "http://iiif.io/api/image/3/context.json",
    "maxHeight": 10000,
    "maxWidth": 10000,
    "maxArea": 100000000,
    "extra_features": [
        "mirroring",
        "regionByPct",
        "regionByPx",
        "regionSquare",
        "rotationArbitrary",
        "sizeByConfinedWh",
        "sizeByH",
        "sizeByPct",
        "sizeByW",
        "sizeByWh",
        "sizeUpscaling"
    ],
    "preferredFormat": [
        "jpg"
    ],
    "extraFormats": [
        "webp",
        "png",
        "tif"
    ],
    "extraQualities": [
        "color",
        "gray",
        "bitonal"
    ]
}

The sample image can be viewed at http://localhost:4000/bentheim.jpg/full/max/0/default.jpg and you can start experimenting with the IIIF API parameters.

Jacob van Ruisdael. Gezicht op kasteel Bentheim, circa 1653

About

An Elixir plug implementing the IIIF image API specification.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •