Skip to content

Commit

Permalink
Merge pull request #44 from zimeon/version3
Browse files Browse the repository at this point in the history
Release first version with v3.0 support
  • Loading branch information
zimeon authored Aug 22, 2019
2 parents 9d10018 + f280ab4 commit 3cc6a42
Show file tree
Hide file tree
Showing 48 changed files with 1,747 additions and 225 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.coverage
htmlcov
*.pyc
*.swp
*~
.DS_store
.cache
Expand Down
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
install:
Expand Down
7 changes: 6 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
iiif changelog
==============

2019-08-21 v1.0.7

- Add preliminary support for IIIF Image API v3.0 in iiif_testserver.py
- Drop support for python 3.4

2018-03-05 v1.0.6

- Drop support for Python 2.6
- Use latest version of Pillow (currently 5.0.0)
- Drop support for python 2.6

2018-02-16 v1.0.5

Expand Down
6 changes: 4 additions & 2 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Supports the `International Image Interoperability Framework
<http://iiif.io/>`_: Image API
`2.1
<http://iiif.io/api/image/2.1/>`_ (and versions
`3.0
<http://iiif.io/api/image/2.3/>`_ PRELIMINARY,
`2.0
<http://iiif.io/api/image/2.0/>`_,
`1.1
Expand All @@ -30,7 +32,7 @@ Installation
------------

The library, test server, static file generator are all designed to
work with Python 2.7, 3.4, 3.5 and 3.6. Manual installation is
work with Python 2.7, 3.5 and 3.6. Manual installation is
necessary to get the demonstration documentation and examples.

**Automatic installation from PyPI**
Expand Down Expand Up @@ -88,7 +90,7 @@ Copyright and License
---------------------

iiif library and programs implementing the IIIF API
Copyright (C) 2012--2018 Simeon Warner
Copyright (C) 2012--2019 Simeon Warner

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# IIIF Image API reference implementation

## Test codes in `docs`

* `check_max_algorithm.py` - Maximum size calculation implementing `maxArea`, `maxHeight`, `maxWidth` as defined in <http://iiif.io/api/image/3.0/#technical-properties>. Code exceprt used in <http://iiif.io/api/image/3.0/implementation/#linked-data-implementation-notes>.
107 changes: 107 additions & 0 deletions docs/check_max_algorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python
"""Code to check algorithm for implementing maxArea, maxHeight, maxWidth.
See: http://iiif.io/api/image/3.0/#technical-properties
"""
import unittest


def aspect_ratio_preserving_resize(width, height, scale):
"""Acpect ratio preserving scaling of width,height."""
if (width <= height):
w = int(width * scale)
h = int(height * float(w) / float(width) + 0.5)
else:
h = int(height * scale)
w = int(width * float(h) / float(height) + 0.5)
return(w, h)


def max(width, height, maxArea=None, maxHeight=None, maxWidth=None):
"""Return /max/ size as w,h given region width,height and constraints."""
# default without constraints
(w, h) = (width, height)
# use size constraints if present, else full
if maxArea and maxArea < (w * h):
# approximate area limit, rounds down to avoid possibility of
# slightly exceeding maxArea
scale = (float(maxArea) / float(w * h)) ** 0.5
w = int(w * scale)
h = int(h * scale)
if maxWidth:
if not maxHeight:
maxHeight = maxWidth
if maxWidth < w:
# calculate wrt original width, height rather than
# w, h to avoid compounding rounding issues
w = maxWidth
h = int(float(height * maxWidth) / float(width) + 0.5)
if maxHeight < h:
h = maxHeight
w = int(float(width * maxHeight) / float(height) + 0.5)
# w, h is possibly constrained size
return(w, h)


class TestStringMethods(unittest.TestCase):
"""Test it..."""

def max(self, width, height, maxArea=None, maxHeight=None, maxWidth=None):
"""Wrapper around max that checks values returned against constraints."""
(w, h) = max(width, height, maxArea, maxHeight, maxWidth)
if maxWidth:
if not maxHeight:
maxHeight = maxWidth
self.assertLessEqual(w, maxWidth)
if maxHeight:
self.assertLessEqual(h, maxHeight)
if maxArea:
self.assertLessEqual(w * h, maxArea)
return(w, h)

def test_no_limit(self):
"""Test cases where no constraints are specified."""
self.assertEqual(self.max(1, 1), (1, 1))
self.assertEqual(self.max(1234, 12345), (1234, 12345))

def test_maxArea(self):
"""Test cases for maxArea constraint."""
self.assertEqual(self.max(1, 1, maxArea=10000), (1, 1))
self.assertEqual(self.max(100, 100, maxArea=10000), (100, 100))
self.assertEqual(self.max(101, 101, maxArea=10000), (100, 100))

def test_maxWidth(self):
"""Test cases for maxWidth constraint."""
self.assertEqual(self.max(1, 1, maxWidth=123), (1, 1))
self.assertEqual(self.max(123, 123, maxWidth=123), (123, 123))
self.assertEqual(self.max(124, 124, maxWidth=123), (123, 123))
self.assertEqual(self.max(6000, 4000, maxWidth=30), (30, 20))
self.assertEqual(self.max(30, 4000, maxWidth=30), (0, 30))

def test_maxWidthHeight(self):
"""Test cases for maxWidth and maxHeight constraints."""
self.assertEqual(self.max(124, 124, maxWidth=123, maxHeight=124), (123, 123))
self.assertEqual(self.max(124, 124, maxWidth=123, maxHeight=122), (122, 122))
self.assertEqual(self.max(6000, 4000, maxWidth=1000, maxHeight=20), (30, 20))
self.assertEqual(self.max(6010, 4000, maxWidth=1000, maxHeight=20), (30, 20))
self.assertEqual(self.max(6000, 12, maxWidth=1000, maxHeight=20), (1000, 2)) # exact
self.assertEqual(self.max(6000, 14, maxWidth=1000, maxHeight=20), (1000, 2))
self.assertEqual(self.max(6000, 15, maxWidth=1000, maxHeight=20), (1000, 3))
self.assertEqual(self.max(6000, 18, maxWidth=1000, maxHeight=20), (1000, 3)) # exact

def test_maxAreaWidthHeight(self):
"""Test cases with combined constraints."""
self.assertEqual(self.max(110, 110, maxArea=10000, maxWidth=110, maxHeight=110), (100, 100))
self.assertEqual(self.max(110, 110, maxArea=20000, maxWidth=110, maxHeight=105), (105, 105))
self.assertEqual(self.max(6000, 1000, maxArea=900000, maxWidth=2000, maxHeight=1000), (2000, 333))
self.assertEqual(self.max(6000, 30, maxArea=900000, maxWidth=2000, maxHeight=1000), (2000, 10))
self.assertEqual(self.max(6000, 6000, maxArea=900000, maxWidth=2000, maxHeight=1000), (948, 948))
self.assertEqual(self.max(4000, 6000, maxArea=900000, maxWidth=2000, maxHeight=1000), (667, 1000))
self.assertEqual(self.max(60, 6000, maxArea=900000, maxWidth=2000, maxHeight=1000), (10, 1000))
self.assertEqual(self.max(6000, 6000, maxArea=900, maxWidth=1000, maxHeight=1000), (30, 30))
self.assertEqual(self.max(6000, 60, maxArea=900, maxWidth=1000, maxHeight=1000), (300, 3))
self.assertEqual(self.max(6000, 10, maxArea=900, maxWidth=1000, maxHeight=1000), (734, 1))


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion iiif/_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""Version number for this IIIF Image API library."""
__version__ = '1.0.6'
__version__ = '1.0.7'
8 changes: 6 additions & 2 deletions iiif/flask_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,13 @@ def prefix_index_page(config):
if (config.include_osd):
body += '<th> </th>'
body += "</tr>\n"
max = 'max' if api_version >= '3.0' else 'full'
for identifier in sorted(ids):
base = urljoin('/', config.client_prefix + '/' + identifier)
body += '<tr><th align="left">%s</th>' % (identifier)
info = base + "/info.json"
body += '<td><a href="%s">%s</a></td>' % (info, 'info')
suffix = "full/full/0/%s" % (default)
suffix = "full/%s/0/%s" % (max, default)
body += '<td><a href="%s">%s</a></td>' % (base + '/' + suffix, suffix)
if (config.klass_name != 'dummy'):
suffix = "full/256,256/0/%s" % (default)
Expand Down Expand Up @@ -316,6 +317,7 @@ def image_information_response(self):
i.formats = ["jpg", "png"] # FIXME - should come from manipulator
if (self.auth):
self.auth.add_services(i)

return self.make_response(i.as_json(),
headers={"Content-Type": self.json_mime_type})

Expand Down Expand Up @@ -369,8 +371,10 @@ def image_request_response(self, path):
self.iiif.format = formats[accept]
(outfile, mime_type) = self.manipulator.derive(file, self.iiif)
# FIXME - find efficient way to serve file with headers
# could this be the answer: https://stackoverflow.com/questions/31554680/how-to-send-header-in-flask-send-file
# currently no headers are sent with the file
self.add_compliance_header()
return send_file(outfile, mimetype=mime_type)
return self.make_response(send_file(outfile, mimetype=mime_type))

def error_response(self, e):
"""Make response for an IIIFError e.
Expand Down
Loading

0 comments on commit 3cc6a42

Please sign in to comment.