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 Thumbnail from IIIF #209

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion iiif_prezi3/helpers/add_thumbnail.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from ..loader import monkeypatch_schema
from ..skeleton import (AccompanyingCanvas, Annotation, AnnotationCollection,
AnnotationPage, Canvas, Collection, Manifest,
PlaceholderCanvas, Range, Reference, ResourceItem)
PlaceholderCanvas, Range, Reference, ResourceItem,
ServiceItem, ServiceItem1)


class AddThumbnail:
Expand All @@ -21,5 +22,75 @@ def add_thumbnail(self, image_url, **kwargs):
self.thumbnail.append(new_thumbnail)
return new_thumbnail

def create_thumbnail_from_iiif(self, url, preferred_width=500, **kwargs):
"""Adds an image thumbnail to a manifest or canvas based on a IIIF service.

Args:
url (str): An HTTP URL which points at a IIIF Image response.
preferred_width (int, optional): the preferred width of the thumbnail.
**kwargs (): see ResourceItem.

Returns:
new_thumbnail (ResourceItem): The updated list of thumbnails, including the newly-created one.
"""
image_response = ResourceItem(id=url, type='Image')
image_info = image_response.set_hwd_from_iiif(url)
context = image_info.get('@context', '')

if 'sizes' in image_info:
best_fit_size = max(
(size for size in image_info['sizes'] if size["width"] <= preferred_width),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should get a bigger image as this can be shrinked with css and not loose quality. If we go smaller css will enlarge it and it may get pixilated.

key=lambda size: size["width"],
default=image_info['sizes'][0]
)
thumbnail_id = f"{url.replace('/info.json', '')}/full/{best_fit_size['width']},{best_fit_size['height']}/0/default.jpg"
else:
thumbnail_id = f"{url.replace('/info.json', '')}/full/full/0/default.jpg" if context == "http://iiif.io/api/image/2/context.json" else f"{url.replace('/info.json', '')}/full/max/0/default.jpg"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this instead generate a image closer to the size provided? Assuming its not a level0 image.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@glenrobson, I still need to refactor for this. Is it safe to say that what's being suggested is if no sizes property exists and the image server is not level 0, this should return a thumbnail request for the exact width. Is this right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if level0 then return max or full
else
use exact side requested (for v2 width/ and for v3 width/(height)) height optional for v3 level 1 and above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still need this else statement?


if context == "http://iiif.io/api/image/2/context.json":
profile_data = image_info.get('profile', [])
if isinstance(profile_data, str):
profile = profile_data
elif isinstance(profile_data, list):
profile = next((item for item in profile_data if isinstance(item, str)), '')
else:
profile = ''
service = ServiceItem1(
id=image_info['@id'],
profile=profile,
type="ImageService2",
format="image/jpeg"
)
if profile == "http://iiif.io/api/image/2/level0.json" and 'sizes' in image_info:
thumbnail_id = f"{url.replace('/info.json', '')}/full/{best_fit_size['width']},/0/default.jpg"
else:
service = ServiceItem(
id=image_info['id'],
profile=image_info.get('profile', ''),
type=image_info.get('type', 'ImageService'),
format="image/jpeg"
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potentially move this up to the else sizes on line 55/56

new_thumbnail = ResourceItem(
id=thumbnail_id,
type='Image',
format="image/jpeg",
**kwargs
)
if 'sizes' in image_info:
new_thumbnail.height = best_fit_size.get('height')
new_thumbnail.width = best_fit_size.get('width')
elif 'height' in image_info and 'width' in image_info:
new_thumbnail.height = image_info['height']
new_thumbnail.width = image_info['width']

if not hasattr(self, 'thumbnail') or self.thumbnail is None:
self.thumbnail = []

new_thumbnail.add_service(service)
self.thumbnail.append(new_thumbnail)

return self.thumbnail


monkeypatch_schema([Canvas, PlaceholderCanvas, AccompanyingCanvas, AnnotationPage, Collection, Manifest, Annotation, Range, ResourceItem, AnnotationCollection, Reference], AddThumbnail)
254 changes: 254 additions & 0 deletions tests/test_add_thumbnail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import unittest
from unittest.mock import Mock, patch

from iiif_prezi3 import Manifest


class AddThumbnailTests(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_thumbnail_from_iiif_v3(self, mockrequest_get):
image_id = 'https://fixtures.iiif.io/other/level0/Glen/photos/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",
"id": "https://fixtures.iiif.io/other/level0/Glen/photos/gottingen",
"type": "ImageService3",
"protocol": "http://iiif.io/api/image",
"profile": "level0",
"width": 4032,
"height": 3024,
"sizes": [
{
"width": 126,
"height": 95
},
{
"width": 252,
"height": 189
},
{
"width": 504,
"height": 378
},
{
"width": 1008,
"height": 756
},
{
"width": 2016,
"height": 1512
},
{
"width": 4032,
"height": 3024
}
]
}

thumbnail = self.manifest.create_thumbnail_from_iiif(image_info_url)[0]

# check thumbnail matches preferred size
self.assertEqual(thumbnail.height, 189)
self.assertEqual(thumbnail.width, 252)

# check thumbnail id
self.assertEqual(thumbnail.id, "https://fixtures.iiif.io/other/level0/Glen/photos/gottingen/full/252,189/0/default.jpg")

# check thumbnail service
self.assertEqual(thumbnail.service[0].profile, "level0")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if you ask for slightly smaller than a size it picks the closest bigger one.

self.assertEqual(thumbnail.service[0].type, "ImageService3")

@patch('iiif_prezi3.helpers.set_hwd_from_iiif.requests.get')
def test_create_thumbnail_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
}

thumbnail = self.manifest.create_thumbnail_from_iiif(image_info_url)[0]

# check thumbnail matches preferred size
self.assertEqual(thumbnail.height, 3024)
self.assertEqual(thumbnail.width, 4032)

# Since info_json has no sizes, use full/full
self.assertEqual(thumbnail.id, "https://iiif.io/api/image/2.1/example/reference/918ecd18c2592080851777620de9bcb5-gottingen/full/full/0/default.jpg")

# check thumbnail service
self.assertEqual(thumbnail.service[0].profile, "http://iiif.io/api/image/2/level1.json")
self.assertEqual(thumbnail.service[0].type, "ImageService2")

@patch('iiif_prezi3.helpers.set_hwd_from_iiif.requests.get')
def test_create_thumbnail_from_level_0_iiif_v3(self, mockrequest_get):
image_id = 'https://iiif-test.github.io/test2/images/IMG_5949'
image_info_url = f'{image_id}/info.json'
mockresponse = Mock(status_code=200)
mockrequest_get.return_value = mockresponse
mockresponse.json.return_value = {
"tiles": [
{
"scaleFactors": [
32,
16,
8,
4,
2,
1
],
"width": 1024,
"height": 1024
}
],
"protocol": "http://iiif.io/api/image",
"sizes": [
{
"width": 126,
"height": 95
},
{
"width": 252,
"height": 189
},
{
"width": 504,
"height": 378
},
{
"width": 1008,
"height": 756
},
{
"width": 2016,
"height": 1512
},
{
"width": 4032,
"height": 3024
}
],
"profile": "level0",
"width": 4032,
"id": "https://iiif-test.github.io/test2/images/IMG_5949",
"type": "ImageService3",
"@context": "http://iiif.io/api/image/3/context.json",
"height": 3024
}

thumbnail = self.manifest.create_thumbnail_from_iiif(image_info_url)[0]
self.assertEqual(thumbnail.height, 189)
self.assertEqual(thumbnail.width, 252)
self.assertEqual(
thumbnail.id,
"https://iiif-test.github.io/test2/images/IMG_5949/full/252,189/0/default.jpg"
)

# check thumbnail service
self.assertEqual(thumbnail.service[0].profile, "level0")
self.assertEqual(thumbnail.service[0].type, "ImageService3")

@patch('iiif_prezi3.helpers.set_hwd_from_iiif.requests.get')
def test_create_thumbnail_from_level_0_iiif_v2(self, mockrequest_get):
image_id = 'https://iiif-test.github.io/test2/images/IMG_8669'
image_info_url = f'{image_id}/info.json'
mockresponse = Mock(status_code=200)
mockrequest_get.return_value = mockresponse
mockresponse.json.return_value = {
"tiles": [
{
"scaleFactors": [
32,
16,
8,
4,
2,
1
],
"width": 1024,
"height": 1024
}
],
"protocol": "http://iiif.io/api/image",
"sizes": [
{
"width": 126,
"height": 95
},
{
"width": 252,
"height": 189
},
{
"width": 504,
"height": 378
},
{
"width": 1008,
"height": 756
},
{
"width": 2016,
"height": 1512
},
{
"width": 4032,
"height": 3024
}
],
"profile": "http://iiif.io/api/image/2/level0.json",
"width": 4032,
"@id": "https://iiif-test.github.io/test2/images/IMG_8669",
"@context": "http://iiif.io/api/image/2/context.json",
"height": 3024
}

thumbnail = self.manifest.create_thumbnail_from_iiif(image_info_url)[0]
self.assertEqual(thumbnail.height, 189)
self.assertEqual(thumbnail.width, 252)
self.assertEqual(
thumbnail.id,
"https://iiif-test.github.io/test2/images/IMG_8669/full/252,/0/default.jpg"
)
Loading