Skip to content
Snippets Groups Projects
Commit deac5cb7 authored by Erwan Rouchet's avatar Erwan Rouchet Committed by Bastien Abadie
Browse files

Handle maxWidth and maxHeight in IIIF images

parent 26214a73
No related branches found
No related tags found
No related merge requests found
......@@ -231,7 +231,6 @@ class Image(S3FileModelMixin, IndexableModel):
"""
Check the image's existence and update width, height and status properties
"""
url = self.url
if not url.endswith('/'):
url += '/'
......@@ -243,7 +242,8 @@ class Image(S3FileModelMixin, IndexableModel):
# Load data
data = resp.json()
assert all(item in data for item in ('@id', 'width', 'height'))
assert all(item in data for item in ('@id', 'width', 'height', 'profile')), 'Missing required properties'
assert isinstance(data['profile'], list), 'Profile is not a list'
# Check id
image_id = data['@id']
......@@ -262,6 +262,32 @@ class Image(S3FileModelMixin, IndexableModel):
self.path = image_id[len(self.server.url) + 1:]
self.width, self.height = int(data['width']), int(data['height'])
# Handle optional maxWidth and maxHeight properties in the profile description
# `profile` is a list with one or more Image API compliance level URIs,
# and optionally an object, the profile description.
max_width, max_height = None, None
if len(data['profile']) >= 2:
profile_desc = next((item for item in data['profile'] if isinstance(item, dict)), {})
max_width = int(profile_desc.get('maxWidth', 0))
max_height = int(profile_desc.get('maxHeight', 0))
# Special case for Harvard IDS: those properties are wrongly defined in the main object
if not max_width and 'maxWidth' in data:
max_width = int(data.get('maxWidth', 0))
if not max_height and 'maxHeight' in data:
max_height = int(data.get('maxHeight', 0))
# From the Image API specification: If maxWidth is specified and maxHeight is not,
# then clients should infer that maxHeight = maxWidth.
if max_width and not max_height:
max_height = max_width
# Update the image size to reflect the maximum allowed size
if (max_width or max_height) and (self.width > max_width or self.height > max_height):
ratio = min(max_width / self.width, max_height / self.height)
self.width = round(ratio * self.width)
self.height = round(ratio * self.height)
self.status = S3FileStatus.Checked
if save:
self.save()
......
......@@ -7,11 +7,6 @@ from unittest.mock import patch
class TestCheckImages(FixtureTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.check_mock = patch('arkindex.images.management.commands.check_images.Image.perform_check').start()
@classmethod
def setUpTestData(cls):
super().setUpTestData()
......@@ -25,29 +20,28 @@ class TestCheckImages(FixtureTestCase):
# Create an image linked to zero elements
cls.imgsrv.images.create(path='am-outside')
def tearDown(self):
super().tearDown()
self.check_mock.reset_mock()
def test_nothing(self):
@patch('arkindex.images.management.commands.check_images.Image.perform_check')
def test_nothing(self, check_mock):
call_command(
'check_images',
)
self.assertEqual(self.check_mock.call_count, 5)
self.assertEqual(check_mock.call_count, 5)
def test_corpus(self):
@patch('arkindex.images.management.commands.check_images.Image.perform_check')
def test_corpus(self, check_mock):
call_command(
'check_images',
corpus=self.corpus,
)
self.assertEqual(self.check_mock.call_count, 4)
self.assertEqual(check_mock.call_count, 4)
def test_element(self):
@patch('arkindex.images.management.commands.check_images.Image.perform_check')
def test_element(self, check_mock):
call_command(
'check_images',
element=[self.p1, self.p2],
)
self.assertEqual(self.check_mock.call_count, 2)
self.assertEqual(check_mock.call_count, 2)
def test_corpus_xor_element(self):
with self.assertRaises(CommandError):
......@@ -57,9 +51,10 @@ class TestCheckImages(FixtureTestCase):
element=[self.p1, ],
)
def test_force(self):
@patch('arkindex.images.management.commands.check_images.Image.perform_check')
def test_force(self, check_mock):
call_command(
'check_images',
force=True,
)
self.assertEqual(self.check_mock.call_count, 7)
self.assertEqual(check_mock.call_count, 7)
from arkindex.project.aws import S3FileStatus
from arkindex.project.tests import FixtureTestCase
import requests
import responses
class TestImagePerformCheck(FixtureTestCase):
def setUp(self):
self.img = self.imgsrv.images.get(path='img1')
@responses.activate
def test_status(self):
"""
Test Image.perform_check fails on a HTTP error code
"""
responses.add(responses.GET, 'http://server/img1/info.json', status=418)
self.img.status = S3FileStatus.Unchecked
with self.assertRaises(requests.HTTPError):
self.img.perform_check(raise_exc=True, save=False)
self.assertEqual(self.img.status, S3FileStatus.Error)
@responses.activate
def test_required_items(self):
"""
Test Image.perform_check fails on a HTTP error code
"""
base_payload = {
'@id': 'http://server/img1',
'width': 42,
'height': 42,
'profile': ['http://iiif.io/api/image/2/level2.json'],
}
for key in base_payload:
payload = base_payload.copy()
del payload[key]
responses.add(responses.GET, 'http://server/img1/info.json', json=payload)
with self.assertRaises(AssertionError, msg='Missing required properties'):
self.img.perform_check(raise_exc=True, save=False)
self.assertEqual(self.img.status, S3FileStatus.Error)
@responses.activate
def test_check_id(self):
"""
Test Image.perform_check ensures the image is on the expected server
"""
responses.add(responses.GET, 'http://server/img1/info.json', json={
'@id': 'http://wrongserver/img1',
'width': 42,
'height': 42,
'profile': ['http://iiif.io/api/image/2/level2.json'],
})
self.img.status = S3FileStatus.Unchecked
with self.assertRaisesRegex(AssertionError, 'Image id does not start with server url'):
self.img.perform_check(raise_exc=True, save=False)
self.assertEqual(self.img.status, S3FileStatus.Error)
@responses.activate
def test_update_fields(self):
"""
Test Image.perform_check updates the path, width and height
"""
responses.add(responses.GET, 'http://server/img1/info.json', json={
'@id': 'http://server/img1111',
'width': 9000,
'height': 8000,
'profile': ['http://iiif.io/api/image/2/level2.json'],
})
self.assertEqual(self.img.path, 'img1')
self.assertEqual(self.img.width, 1000)
self.assertEqual(self.img.height, 1000)
self.img.status = S3FileStatus.Unchecked
self.img.perform_check(raise_exc=True, save=False)
self.assertEqual(self.img.status, S3FileStatus.Checked)
self.assertEqual(self.img.path, 'img1111')
self.assertEqual(self.img.width, 9000)
self.assertEqual(self.img.height, 8000)
@responses.activate
def test_max_size(self):
"""
Test Image.perform_check handles the IIIF maxWidth and maxHeight optional properties
"""
responses.add(responses.GET, 'http://server/img1/info.json', json={
'@id': 'http://server/img1',
'width': 9000,
'height': 8000,
'profile': [
'http://iiif.io/api/image/2/level2.json',
{
"maxWidth": 4500,
"maxHeight": 10000,
}
],
})
self.img.status = S3FileStatus.Unchecked
self.img.perform_check(raise_exc=True, save=False)
self.assertEqual(self.img.status, S3FileStatus.Checked)
self.assertEqual(self.img.width, 4500)
self.assertEqual(self.img.height, 4000)
@responses.activate
def test_max_height_deduced(self):
"""
Test Image.perform_check assumes maxHeight is equal to maxWidth
when maxHeight is missing and maxWidth is defined
"""
responses.add(responses.GET, 'http://server/img1/info.json', json={
'@id': 'http://server/img1',
'width': 1000, # width < maxWidth
'height': 8000, # height > maxWidth
'profile': [
'http://iiif.io/api/image/2/level2.json',
{
"maxWidth": 4000,
}
],
})
self.img.status = S3FileStatus.Unchecked
self.img.perform_check(raise_exc=True, save=False)
self.assertEqual(self.img.status, S3FileStatus.Checked)
self.assertEqual(self.img.width, 500)
self.assertEqual(self.img.height, 4000)
@responses.activate
def test_harvard_non_conform(self):
"""
Test Image.perform_check falls back to properties from the Image Information object
when they are not defined in the profile description object or when it is missing.
"""
responses.add(responses.GET, 'http://server/img1/info.json', json={
'@id': 'http://server/img1',
'width': 9001,
'maxWidth': 9000,
'height': 9001,
'maxHeight': 8999,
'profile': ['http://iiif.io/api/image/2/level2.json'],
})
self.img.status = S3FileStatus.Unchecked
self.img.perform_check(raise_exc=True, save=False)
self.assertEqual(self.img.status, S3FileStatus.Checked)
self.assertEqual(self.img.width, 8999)
self.assertEqual(self.img.height, 8999)
......@@ -3,3 +3,4 @@ tripoli
django-nose
coverage
uritemplate==3
responses
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment