Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create canvas from IIIF Image #89

Merged
merged 11 commits into from Nov 11, 2022
15 changes: 8 additions & 7 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,9 +56,9 @@ def jsonld(self, **kwargs):
pydantic_args = ["include", "exclude", "by_alias", "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])
return json.dumps({"@context": "http://iiif.io/api/presentation/3/context.json", **self.dict(exclude_unset=False, exclude_defaults=False, exclude_none=True, **dict_kwargs)}, **json_kwargs)
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)
digitaldogsbody marked this conversation as resolved.
Show resolved Hide resolved

def jsonld_dict(self, **kwargs):
pydantic_args = ["include", "exclude", "by_alias", "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, **dict_kwargs)}
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])
9 changes: 6 additions & 3 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 @@ -11,7 +11,8 @@ def set_hwd_from_iiif(self, url):
"""Set height and width on a Canvas object.

Requests IIIF Image information remotely for an
image resource and sets resulting height and width.
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
130 changes: 130 additions & 0 deletions tests/test_create_canvas_from_iiif.py
@@ -0,0 +1,130 @@
import unittest
from unittest.mock import Mock, patch

import requests

from iiif_prezi3 import Canvas, 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")