sul-dlss-deprecated / iiifManifestLayouts

Other
10 stars 5 forks source link

Provide factory service for thumbnail URLs based on manifest #98

Open iangilman opened 9 years ago

iangilman commented 9 years ago

@nein09 will need this for #97.

aeschylus commented 9 years ago

Factory for IIIF Resource Objects

The factory will produce properly configured IIIF resource objects that have the information they need from the IIIF Metadata that is passed to them. There is a lot of checking that needs to go on to determine whether the resource is static or dynamic, is an alternate of another image, what its clip mask should be (if any), and what its local coordinates should be in the canvas space. The relevant specification sections are 6.4 Image resources and 7.1 Segments, and 7.4 Choice of Alternate Resources. You will notice language such as "MAY", "SHOULD", and "MUST", which will affect the implementation.

Ultimately, we want some uniform configuration for a resourceObject. Presumably these properties will be:

Possible Metadata Structures for Image and Resource Metadata

There are 3 possible ways the image/resource may be represented structurally, based on their @type. Note that both oa:SpecificResources or dcterms:Image resource types may appear inside of oa:Choice options, and that eventually one does always "drill down" to a vanilla dcterms:Image.

Resource is of type dcterms:Image

The simplest version is just that we have an image with a static resource (no service with the correct @context). We dereference the image and assume that it has the same aspect ratio and size as the canvas in the on field (because there is no hash identifier in the on field).

{
  "@context":"http://iiif.io/api/presentation/2/context.json",
  "@id":"http://www.example.org/iiif/book1/annotation/p0001-image",
  "@type":"oa:Annotation",
  "motivation":"sc:painting",
  "resource": {
    "@id":"http://www.example.org/iiif/book1/res/page1.jpg",
    "@type":"dctypes:Image",
    "format":"image/jpeg",
    "service": {
      "@context": "http://iiif.io/api/image/2/context.json",
      "@id":"http://www.example.org/images/book1-page1",
      "profile":"http://iiif.io/api/image/2/profiles/level2.json",
    },
  },
  "on":"http://www.example.org/iiif/book1/canvas/p1"
}

If the @id image has a hash identifier in it, then we need to forcibly crop it (so pass in a clipMask).

{
      "@context":"http://iiif.io/api/presentation/2/context.json",
      "@id":"http://www.example.org/iiif/book1/annotation/anno1",
      "@type":"oa:Annotation",
      "motivation":"sc:painting",
      "resource":{
        "@id":"http://www.example.org/iiif/book1/res/page1.jpg#xywh=40,50,1200,1800",
        "@type":"dctypes:Image",
        "format":"image/jpeg"
      },
      "on":"http://www.example.org/iiif/book1/canvas/p1"
    }

Note that the on field has no hash identifier, so we assume our tilesource will have the same size and aspect ratio of the canvas.

If the "on" field has a hash identifier (`"on":"http://www.example.org/iiif/book1/canvas/p1#xywh=100,124,300,400"), then the destination x, y, width, and height (in "canvas units") is given. No other data must be consulted to determine the eventual x, y, width, and height of an image resource (including alternate/choice images).

Resource type is oa:Choice

{
  "@context":"http://iiif.io/api/presentation/2/context.json",
  "@id":"http://www.example.org/iiif/book1/annotation/anno1",
  "@type":"oa:Annotation",
  "motivation":"sc:painting",
  "resource":{
    "@type":"oa:Choice",
    "default":{
      "@id":"http://www.example.org/iiif/book1/res/page1.jpg",
      "@type":"dctypes:Image",
      "label":"Color"
    },
    "item": [
      {
        "@id":"http://www.example.org/iiif/book1/res/page1-blackandwhite.jpg",
        "@type":"dctypes:Image",
        "label":"Black and White"
      }
    ]
  },
  "on":"http://www.example.org/iiif/book1/canvas/p1"
}

Resource is of type oa:SpecificResource

{
  "@context":"http://iiif.io/api/presentation/2/context.json",
  "@id":"http://www.example.org/iiif/book1/annotation/anno1",
  "@type":"oa:Annotation",
  "motivation":"sc:painting",
  "resource":{
    "@id" : "http://www.example.org/iiif/book1-page1/40,50,1200,1800/full/0/default.jpg",
    "@type":"oa:SpecificResource",
    "full": {
      "@id":"http://www.example.org/iiif/book1-page1/full/full/0/default.jpg",
      "@type":"dctypes:Image",
      "service": {
        "@context": "http://iiif.io/api/image/2/context.json",
        "@id": "http://www.example.org/iiif/book1-page1",
        "profile":"http://iiif.io/api/image/2/level2.json"
      }
    },
    "selector": {
      "@context": "http://iiif.io/api/annex/openannotation/context.json",
      "@type": "iiif:ImageApiSelector",
      "region": "40,50,1200,1800"
    }
  },
  "on":"http://www.example.org/iiif/book1/canvas/p1#xywh=50,50,320,240"
}

Position and Size

This is the easy part. No matter what, the x, y, width, and height of the resource are given by the on property of the image metadata. If there is no hash identifier, then x=0, y=0, width=canvasWidth, height=canvasHeight, and the aspect ratio is assumed to be the same as the canvas. If there is a hash identifier, such as "on":"http://www.example.org/iiif/book1/canvas/p1#xywh=50,50,320,240", then the position and size properties are given by the properties there, in canvas units. So, for instance, x=50, y=50, width=320, height=240, where these data are in "canvas units".

So if the canvas of which these items are a part is 1000X2000 (0.5 aspect ratio), then the x and y will be 5% and 2.5% of the total canvas width and height, while the width and height of the resource when it gets placed in the OSD coordinate space will be 32% and and 24% of the canvas width and height in the OSD space.

Also, if this hash identifier exists, we know this is a "detail" image, though there is no real distinction that will make itself into an interface (There is no privileged image that is considered a "main" image; a canvas may be depicted by 3 or 4 "detail" images, for instance).

Alternates

A resource can have an @type of 'oa:choice, oa:SpecificResource, and dctypes:Image. We need to check for them in this order, since oa:SpecificResource resources can appear inside of a choice as well as dctypes:Image resources. And ultimately, the next lowest level inside an oa:SpecificResource will also be a dctypes:Image resource. It makes no difference whether the image metadata implies this resource will supply a "detail" image, there may still be a choice of many alternate representations of this same detail image.

As described in 7.3, an alternate resource will change the structure of the resource metadata, so we will need to check for this different structure, and create a new resourceObject for each image choice found. Inside resource, we should check whether it has a default field. If so, then the contents of default is the "default" resource for that group. Other resources are in the item array, each of whose elements has normal resource metadata that can undergo further processing like any resource. If the resource has no default field, it will be either a "main/plain/detail" image.

If the image is in the item field, it would be good to store the @id of the default somewhere on each of the alternates, so we can refer back to it. This might be useful for rendering a dropdown or tree with the alternate images grouped underneath their default. Many image choice resources may have several dozens possible alternative images, so we need to know which one is the default, and only render that until others are requested.

Static or Dynamic

The "resource" has all the information necessary to construct the resourceObject. The resource may or may not be a "choice" resource, so before we can check for its static or dynamic status, we need to see if the "resource" property has a "default" field, (as above) and get the contents of that. (See 7.3). The following therefore only applies to dctypes:Image resources.

If the resource is dynamic, it "SHOULD" have a "service" property defined. However, it is not enough to just check if the service is defined, because it may be embedded, as described in the annex. It also may not be an image service, but one of a variety of valid services. So we must also check both that the @context is correct (http://iiif.io/api/image/2/context.json) to see if it's the right kind of service, and some required property, like width (a required property of the info.json), to see if it is embedded. If no width is defined, then dereference the @id to retrieve the info.json. If it is embedded, then the info.json is pre-defined, and may be passed in directly.

Since the image may not have a service defined even though one is available, there is some checking we can do on the url to determine if it is a iiif endpoint and then prune it to use it as the base url to retrieve the info.json. However, this may not be advisable in all cases, so let's not bother with that here. The manifest publisher may not want the endpoint to be used in that manner if they haven't published it as such.

Given all of this, the logic should be:

  1. Check that a service with the correct @context exists. If it doesn't, treat it as a static image.
  2. If it does check a required property like width to see if it is embedded. If it is not, store the url. If it is embedded, store the embedded info.json.

Clip Region and "Detail" Images

The clip region for a resource must be retrieved in different ways depending on the @type and whether the resource is static or dynamic. Again, here, oa:Choice resources must be broken into multiple resources and decomposed until the dctypes:Image resource is retrieved.

If the resource is of type oa:SpecificResource, then the clip mask will be given by the selector.region field. You will also have to confirm that the @type of the OA selector is iiif:ImageApiSelector. The service url will need to be retrieved from the full.service[@id].

If the resource is of type dcterms:Image, there are two possibilities. Either there is no hash identifier, in which case the clip mask will be 0,0,canvasWidth, canvasHeight, or there will be a hash identifier. If there is a hash identifier, the image will be static, and the clip mask will be given by that hash identifier. In that case, the static image will need to be processed by osd to prevent the extraneous parts from being shown within the target region (on field hash identifier).

Getting a Thumbnail for the Canvas.

In descriptive properties, the canvas might have a thumbnail, and "should" if there are multiple resources. If the canvas does not have a thumbnail, then construct one from the first image resource.

To do this, simply use the @id of the first image resource (assuming there is one). Otherwise, use some kind of "blank canvas" placeholder, such as an arty, thin-lined svg "X". There may be instances where the given @id image is not as appropriate as a larger or different resolution thumbnail. In this case, if the resource provides a service, and there is a compelling reason to use this service rather than the @id we can construct a iiif url for the desired thumbnail size from the service. Otherwise, just use the @id, which is required by the spec ("MUST").

Note that it is of course possible to construct a thumbnail from all the resources, rendering a somewhat accurate depiction of it including detail images, for instance, using a canvas element to generate a dataurl for the image, but that would be rather intensive.