Skip to content

Commit

Permalink
Merge pull request #89 from iiif-prezi/issue-10
Browse files Browse the repository at this point in the history
Create canvas from IIIF Image
  • Loading branch information
digitaldogsbody committed Nov 11, 2022
2 parents adbaa06 + e80ff93 commit 74882ba
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 10 deletions.
13 changes: 8 additions & 5 deletions iiif_prezi3/base.py
@@ -1,7 +1,6 @@
import json

from pydantic import BaseModel

from pydantic import AnyUrl, BaseModel

class Base(BaseModel):

Expand All @@ -10,16 +9,18 @@ class Config:
validate_all = True
copy_on_model_validation = False
smart_union = True
# Allow us to use the field name like service.id rather than service.@id
allow_population_by_field_name = True

def __getattribute__(self, prop):
val = super(Base, self).__getattribute__(prop)
# __root__ is a custom pydantic thing
if hasattr(val, '__root__'):
if type(val.__root__) in [dict, list, float, int]:
return val.__root__
else:
if type(val.__root__) in [AnyUrl]:
# cast it to a string
return str(val.__root__)
else:
return val.__root__
else:
return val

Expand Down Expand Up @@ -55,12 +56,14 @@ def jsonld(self, **kwargs):
excluded_args = ["exclude_unset", "exclude_defaults", "exclude_none", "by_alias"]
pydantic_args = ["include", "exclude", "encoder"]
dict_kwargs = dict([(arg, kwargs[arg]) for arg in kwargs.keys() if arg in pydantic_args])

json_kwargs = dict([(arg, kwargs[arg]) for arg in kwargs.keys() if arg not in pydantic_args + excluded_args])
return json.dumps({"@context": "http://iiif.io/api/presentation/3/context.json",
**self.dict(exclude_unset=False, exclude_defaults=False, exclude_none=True, by_alias=True, **dict_kwargs)}, **json_kwargs)

def jsonld_dict(self, **kwargs):
pydantic_args = ["include", "exclude", "encoder"]
dict_kwargs = dict([(arg, kwargs[arg]) for arg in kwargs.keys() if arg in pydantic_args])

return {"@context": "http://iiif.io/api/presentation/3/context.json",
**self.dict(exclude_unset=False, exclude_defaults=False, exclude_none=True, by_alias=True, **dict_kwargs)}
1 change: 1 addition & 0 deletions iiif_prezi3/helpers/__init__.py
Expand Up @@ -6,6 +6,7 @@
from .auto_fields import * # noqa: F401,F403
from .canvas_helpers import AddImageToCanvas # noqa: F401
from .canvas_sizes import MaxHelper, MinHelper # noqa: F401
from .create_canvas_from_iiif import CreateCanvasFromIIIF # noqa: F401
from .make_canvas import MakeCanvas # noqa: F401
from .make_collection import MakeCollection # noqa: F401
from .make_manifest import MakeManifest # noqa: F401
Expand Down
59 changes: 59 additions & 0 deletions iiif_prezi3/helpers/create_canvas_from_iiif.py
@@ -0,0 +1,59 @@

from ..loader import monkeypatch_schema
from ..skeleton import (Annotation, AnnotationPage, Canvas, Manifest,
ResourceItem, ServiceItem, ServiceItem1)


class CreateCanvasFromIIIF:
# should probably be added to canvas helpers

def create_canvas_from_iiif(self, url, **kwargs):
"""Create a canvas from a IIIF Image URL.
Creates a canvas from a IIIF Image service passing any
kwargs to the Canvas. Returns a Canvas object
"""
canvas = Canvas(**kwargs)

body = ResourceItem(id="http://example.com", type="Image")
infoJson = body.set_hwd_from_iiif(url)

# Will need to handle IIIF 2...
if 'type' not in infoJson:
# Assume v2

# V2 profile contains profile URI plus extra features
profile = ''
for item in infoJson['profile']:
if isinstance(item, str):
profile = item
break

service = ServiceItem1(id=infoJson['@id'], profile=profile, type="ImageService2")
body.service = [service]
body.id = f'{infoJson["@id"]}/full/full/0/default.jpg'
body.format = "image/jpeg"
else:
service = ServiceItem(id=infoJson['id'], profile=infoJson['profile'], type=infoJson['type'])
body.service = [service]
body.id = f'{infoJson["id"]}/full/max/0/default.jpg'
body.format = "image/jpeg"

annotation = Annotation(motivation='painting', body=body, target=canvas.id)

annotationPage = AnnotationPage()
annotationPage.add_item(annotation)

canvas.add_item(annotationPage)
canvas.set_hwd(infoJson['height'], infoJson['width'])

return canvas

def make_canvas_from_iiif(self, url, **kwargs):
canvas = self.create_canvas_from_IIIF(url, **kwargs)

self.add_item(canvas)


monkeypatch_schema(Manifest, [CreateCanvasFromIIIF])
7 changes: 5 additions & 2 deletions iiif_prezi3/helpers/set_hwd_from_iiif.py
@@ -1,7 +1,7 @@
import requests

from ..loader import monkeypatch_schema
from ..skeleton import Canvas
from ..skeleton import Canvas, Resource, ResourceItem


class SetHwdFromIIIF:
Expand All @@ -12,6 +12,7 @@ def set_hwd_from_iiif(self, url):
Requests IIIF Image information remotely for an
image resource and sets resulting height and width.
This method will return the info.json
Args:
url (str): An HTTP URL for the IIIF image endpoint.
Expand All @@ -31,5 +32,7 @@ def set_hwd_from_iiif(self, url):
resource_info = response.json()
self.set_hwd(resource_info.get("height"), resource_info.get("width"))

return resource_info

monkeypatch_schema(Canvas, [SetHwdFromIIIF])

monkeypatch_schema([Canvas, Resource, ResourceItem], SetHwdFromIIIF)
4 changes: 2 additions & 2 deletions iiif_prezi3/skeleton.py
Expand Up @@ -320,8 +320,8 @@ class ServiceItem(Class):


class ServiceItem1(Base):
_id: Id = Field(..., alias='@id')
_type: str = Field(..., alias='@type')
id: Id = Field(..., alias='@id')
type: str = Field(..., alias='@type')
profile: Optional[str] = None
service: Optional[Service] = None

Expand Down
3 changes: 2 additions & 1 deletion tests/test_basic.py
Expand Up @@ -85,7 +85,8 @@ def testLabel(self):
manifest = Manifest(id='http://iiif.example.org/prezi/Manifest/0', type='Manifest', label={'en': ['default label']})

self.assertTrue('en' in manifest.label, 'Manifest seems to be missing English label')
self.assertEqual(manifest.label['en'], ['default label'], 'Unexpected label for manifest')
self.assertEqual(manifest.label['en'][0], 'default label', 'Unexpected label for manifest')



if __name__ == '__main__':
Expand Down
128 changes: 128 additions & 0 deletions tests/test_create_canvas_from_iiif.py
@@ -0,0 +1,128 @@
import unittest
from unittest.mock import Mock, patch

from iiif_prezi3 import Manifest


class CreateCanvasFromIIIFTests(unittest.TestCase):

def setUp(self):
self.manifest = Manifest(label={'en': ['Manifest label']})

@patch('iiif_prezi3.helpers.set_hwd_from_iiif.requests.get')
def test_create_canvas_from_iiif_v3(self, mockrequest_get):
image_id = 'https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen'
image_info_url = f'{image_id}/info.json'

# successful response with dimensions
mockresponse = Mock(status_code=200)
mockrequest_get.return_value = mockresponse
# set mock to return minimal image api response
mockresponse.json.return_value = {
"@context": "http://iiif.io/api/image/3/context.json",
"extraFormats": ["jpg", "png"],
"extraQualities": ["default", "color", "gray"],
"height": 3024,
"id": "https://iiif.io/api/image/3.0/example/reference/918ecd18c2592080851777620de9bcb5-gottingen",
"profile": "level1",
"protocol": "http://iiif.io/api/image",
"tiles": [{
"height": 512,
"scaleFactors": [1, 2, 4],
"width": 512
}],
"type": "ImageService3",
"width": 4032
}

canvas = self.manifest.create_canvas_from_iiif(image_info_url, label={'en': ['Canvas label']})

# Check canvas params made it through
self.assertEqual(canvas.label,
{'en': ['Canvas label']})

# check canvas dimensions
self.assertEqual(canvas.height, 3024)
self.assertEqual(canvas.width, 4032)

# Check annotation
annotation = canvas.items[0].items[0]
self.assertEqual(annotation.motivation, "painting")

# Check resource
resource = annotation.body

self.assertEqual(resource.id, f'{image_id}/full/max/0/default.jpg')
self.assertEqual(resource.type, 'Image')
self.assertEqual(resource.format, 'image/jpeg')
self.assertEqual(resource.height, 3024)
self.assertEqual(resource.width, 4032)

# Check service
service = resource.service[0]

self.assertEqual(service.id, image_id)
self.assertEqual(service.profile, "level1")
self.assertEqual(service.type, "ImageService3")

@patch('iiif_prezi3.helpers.set_hwd_from_iiif.requests.get')
def test_create_canvas_from_iiif_v2(self, mockrequest_get):
image_id = 'https://iiif.io/api/image/2.1/example/reference/918ecd18c2592080851777620de9bcb5-gottingen'
image_info_url = f'{image_id}/info.json'

# successful response with dimensions
mockresponse = Mock(status_code=200)
mockrequest_get.return_value = mockresponse
# set mock to return minimal image api response
mockresponse.json.return_value = {
"@context": "http://iiif.io/api/image/2/context.json",
"@id": "https://iiif.io/api/image/2.1/example/reference/918ecd18c2592080851777620de9bcb5-gottingen",
"height": 3024,
"profile": [
"http://iiif.io/api/image/2/level1.json",
{
"formats": [ "jpg", "png" ],
"qualities": [ "default", "color", "gray" ]
}
],
"protocol": "http://iiif.io/api/image",
"tiles": [
{
"height": 512,
"scaleFactors": [ 1, 2, 4 ],
"width": 512
}
],
"width": 4032
}

canvas = self.manifest.create_canvas_from_iiif(image_info_url, label={'en': ['Canvas label']})

# Check canvas params made it through
self.assertEqual(canvas.label,
{'en': ['Canvas label']})

# check canvas dimensions
self.assertEqual(canvas.height, 3024)
self.assertEqual(canvas.width, 4032)

# Check annotation
annotation = canvas.items[0].items[0]
self.assertEqual(annotation.motivation, "painting")

# Check resource
resource = annotation.body

self.assertEqual(resource.id, f'{image_id}/full/full/0/default.jpg')
self.assertEqual(resource.type, 'Image')
self.assertEqual(resource.format, 'image/jpeg')
self.assertEqual(resource.height, 3024)
self.assertEqual(resource.width, 4032)

# Check service
service = resource.service[0]

self.assertEqual(service.id, image_id)
self.assertEqual(service.profile, "http://iiif.io/api/image/2/level1.json")
self.assertEqual(service.type, "ImageService2")

0 comments on commit 74882ba

Please sign in to comment.