riiif 2.0.0.beta2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +2 -11
  3. data/.rubocop_todo.yml +76 -33
  4. data/README.md +17 -2
  5. data/app/controllers/riiif/images_controller.rb +8 -2
  6. data/app/models/riiif/image.rb +15 -20
  7. data/app/models/riiif/image_information.rb +12 -24
  8. data/app/services/riiif/crop.rb +99 -30
  9. data/app/services/riiif/image_magick_info_extractor.rb +9 -2
  10. data/app/services/riiif/imagemagick_command_factory.rb +9 -4
  11. data/app/services/riiif/kakadu_command_factory.rb +2 -2
  12. data/app/services/riiif/resize.rb +72 -7
  13. data/app/transformers/riiif/kakadu_transformer.rb +25 -2
  14. data/docs/benchmark.md +75 -0
  15. data/lib/riiif/engine.rb +2 -1
  16. data/lib/riiif/routes.rb +3 -0
  17. data/lib/riiif/version.rb +1 -1
  18. data/riiif.gemspec +3 -3
  19. data/spec/controllers/riiif/images_controller_spec.rb +14 -3
  20. data/spec/models/riiif/image_information_spec.rb +2 -10
  21. data/spec/models/riiif/image_spec.rb +10 -16
  22. data/spec/services/riiif/imagemagick_command_factory_spec.rb +6 -6
  23. data/spec/services/riiif/kakadu_command_factory_spec.rb +15 -15
  24. data/spec/transformers/riiif/kakadu_transformer_spec.rb +22 -22
  25. metadata +24 -46
  26. data/app/models/riiif/transformation.rb +0 -35
  27. data/app/services/riiif/imagemagick_transformer.rb +0 -8
  28. data/app/services/riiif/option_decoder.rb +0 -88
  29. data/app/services/riiif/region/absolute.rb +0 -23
  30. data/app/services/riiif/region/full.rb +0 -23
  31. data/app/services/riiif/region/percentage.rb +0 -68
  32. data/app/services/riiif/region/square.rb +0 -45
  33. data/app/services/riiif/size/absolute.rb +0 -39
  34. data/app/services/riiif/size/best_fit.rb +0 -18
  35. data/app/services/riiif/size/full.rb +0 -17
  36. data/app/services/riiif/size/height.rb +0 -24
  37. data/app/services/riiif/size/percent.rb +0 -44
  38. data/app/services/riiif/size/width.rb +0 -24
  39. data/spec/models/riiif/transformation_spec.rb +0 -42
  40. data/spec/services/riiif/region/absolute_spec.rb +0 -17
  41. data/spec/services/riiif/size/absolute_spec.rb +0 -17
  42. data/spec/services/riiif/size/height_spec.rb +0 -13
  43. data/spec/services/riiif/size/width_spec.rb +0 -13
@@ -10,8 +10,15 @@ module Riiif
10
10
  end
11
11
 
12
12
  def extract
13
- height, width = Riiif::CommandRunner.execute("#{external_command} -format %hx%w #{@path}[0]").split('x')
14
- { height: Integer(height), width: Integer(width) }
13
+ height, width, format = Riiif::CommandRunner.execute(
14
+ "#{external_command} -format '%h %w %m' #{@path}[0]"
15
+ ).split(' ')
16
+
17
+ {
18
+ height: Integer(height),
19
+ width: Integer(width),
20
+ format: format
21
+ }
15
22
  end
16
23
  end
17
24
  end
@@ -44,8 +44,12 @@ module Riiif
44
44
  transformation.format == 'jpg'.freeze
45
45
  end
46
46
 
47
+ def layer_spec
48
+ '[0]' if info.format =~ /pdf/i
49
+ end
50
+
47
51
  def input
48
- " #{path}"
52
+ " #{path}#{layer_spec}"
49
53
  end
50
54
 
51
55
  # pipe the output to STDOUT
@@ -54,17 +58,18 @@ module Riiif
54
58
  end
55
59
 
56
60
  def crop
57
- directive = transformation.crop.to_imagemagick
61
+ directive = Crop.new(transformation.region, info).to_imagemagick
58
62
  " -crop #{directive}" if directive
59
63
  end
60
64
 
61
65
  def size
62
- directive = transformation.size.to_imagemagick
66
+ directive = Resize.new(transformation.size, info).to_imagemagick
63
67
  " -resize #{directive}" if directive
64
68
  end
65
69
 
66
70
  def rotation
67
- " -virtual-pixel white +distort srt #{transformation.rotation}" if transformation.rotation
71
+ return if transformation.rotation.zero?
72
+ " -virtual-pixel white +distort srt #{transformation.rotation}"
68
73
  end
69
74
 
70
75
  def quality
@@ -25,7 +25,7 @@ module Riiif
25
25
  end
26
26
 
27
27
  def reduction_factor
28
- @reduction_factor ||= transformation.size.reduction_factor
28
+ @reduction_factor ||= Resize.new(transformation.size, info).reduction_factor
29
29
  end
30
30
 
31
31
  private
@@ -47,7 +47,7 @@ module Riiif
47
47
  end
48
48
 
49
49
  def region
50
- region_arg = transformation.crop.to_kakadu
50
+ region_arg = Crop.new(transformation.region, info).to_kakadu
51
51
  " -region \"#{region_arg}\"" if region_arg
52
52
  end
53
53
 
@@ -1,30 +1,89 @@
1
1
  module Riiif
2
2
  # Represents a resize operation
3
3
  class Resize
4
- attr_reader :image_info
4
+ # @param size [IIIF::Image::Size] the result the user requested
5
+ # @param image_info []
6
+ def initialize(size, image_info)
7
+ @size = size
8
+ @image_info = image_info
9
+ end
10
+
11
+ attr_reader :image_info, :size
12
+
13
+ # @return [String] a resize directive for imagemagick to use
14
+ def to_imagemagick
15
+ case size
16
+ when IIIF::Image::Size::Percent
17
+ "#{size.percentage}%"
18
+ when IIIF::Image::Size::Width
19
+ size.width
20
+ when IIIF::Image::Size::Height
21
+ "x#{size.height}"
22
+ when IIIF::Image::Size::Absolute
23
+ "#{size.width}x#{size.height}!"
24
+ when IIIF::Image::Size::BestFit
25
+ "#{size.width}x#{size.height}"
26
+ when IIIF::Image::Size::Max, IIIF::Image::Size::Full
27
+ nil
28
+ else
29
+ raise "unknown size #{size.class}"
30
+ end
31
+ end
5
32
 
6
33
  # @return [Integer] the height in pixels
7
34
  def height
8
- image_info.height
35
+ case size
36
+ when IIIF::Image::Size::Absolute
37
+ size.height
38
+ when IIIF::Image::Size::Percent
39
+ image_info.height * Integer(size.percentage).to_f / 100
40
+ when IIIF::Image::Size::Width
41
+ size.height_for_aspect_ratio(image_info.aspect)
42
+ else
43
+ image_info.height
44
+ end
9
45
  end
10
46
 
11
47
  # @return [Integer] the width in pixels
12
48
  def width
13
- image_info.width
49
+ case size
50
+ when IIIF::Image::Size::Absolute
51
+ size.width
52
+ when IIIF::Image::Size::Percent
53
+ image_info.width * Integer(size.percentage).to_f / 100
54
+ when IIIF::Image::Size::Height
55
+ size.width_for_aspect_ratio(image_info.aspect)
56
+ else
57
+ image_info.width
58
+ end
14
59
  end
15
60
 
16
61
  # Should we reduce this image with KDU?
17
62
  def reduce?
18
- true
63
+ case size
64
+ when IIIF::Image::Size::Full, IIIF::Image::Size::Max
65
+ false
66
+ when IIIF::Image::Size::Absolute
67
+ aspect_ratio = width.to_f / height
68
+ in_delta?(image_info.aspect, aspect_ratio, 0.001)
69
+ else
70
+ true
71
+ end
19
72
  end
20
73
 
21
74
  # This is used for a second resize by imagemagick after resizing
22
75
  # by kdu.
23
76
  # No need to scale most resize operations (only percent)
24
77
  # @param [Integer] factor to scale by
25
- # @return [Absolute] a copy of self if factor is zero.
26
- def reduce(_factor)
27
- dup
78
+ # @return [IIIF::Image::Size] a copy of self if factor is zero.
79
+ def reduce(factor)
80
+ case size
81
+ when IIIF::Image::Size::Percent
82
+ pct = size.percentage * 2**factor
83
+ IIIF::Image::Size::Percent.new(pct)
84
+ else
85
+ size.dup
86
+ end
28
87
  end
29
88
 
30
89
  # @return [Integer] the reduction factor for this operation
@@ -41,5 +100,11 @@ module Riiif
41
100
  end
42
101
  factor
43
102
  end
103
+
104
+ private
105
+
106
+ def in_delta?(x1, x2, delta)
107
+ (x1 - x2).abs <= delta
108
+ end
44
109
  end
45
110
  end
@@ -23,15 +23,38 @@ module Riiif
23
23
  def post_process(intermediate_file, reduction_factor)
24
24
  # Calculate a new set of transforms with respect to reduction_factor
25
25
  transformation = if reduction_factor
26
- self.transformation.without_crop(image_info).reduce(reduction_factor)
26
+ reduce(without_crop, reduction_factor)
27
27
  else
28
- self.transformation.without_crop(image_info)
28
+ without_crop
29
29
  end
30
30
  Riiif::File.new(intermediate_file).extract(transformation, image_info)
31
31
  end
32
32
 
33
33
  private
34
34
 
35
+ # Create a clone of the Transformation, without the crop
36
+ # @return [IIIF::Image::Transformation] a new transformation
37
+ def without_crop
38
+ IIIF::Image::Transformation.new(region: IIIF::Image::Region::Full.new,
39
+ size: transformation.size.dup,
40
+ quality: transformation.quality,
41
+ rotation: transformation.rotation,
42
+ format: transformation.format)
43
+ end
44
+
45
+ # Create a clone of this Transformation, scaled by the factor
46
+ # @param [IIIF::Image::Transformation] transformation the transformation to clone
47
+ # @param [Integer] factor the scale for the new transformation
48
+ # @return [Transformation] a new transformation, scaled by factor
49
+ def reduce(transformation, factor)
50
+ resize = Resize.new(transformation.size, image_info)
51
+ IIIF::Image::Transformation.new(region: transformation.region.dup,
52
+ size: resize.reduce(factor),
53
+ quality: transformation.quality,
54
+ rotation: transformation.rotation,
55
+ format: transformation.format)
56
+ end
57
+
35
58
  def tmp_path
36
59
  @link_path ||= LinkNameService.create
37
60
  end
@@ -0,0 +1,75 @@
1
+ # Benchmarks demonstrating different backends.
2
+
3
+ This benchmarks were run on Riiif 2.0.0.beta. They show the difference in speed in decoding a 5240x7057 pixel jp2 using openjpeg 2.2.0 (via Imagemagick) versus Kakadu 7.10.2.
4
+
5
+ ## RIIIF with openjpeg 2.2.0
6
+
7
+ ```
8
+ $ ab -n 30 'https://localhost:3000/iiif/2/bc%2F151%2Fbq%2F1744%2Fbc151bq1744_00_0001.jp2/full/!400,400/0/default.jpg'
9
+
10
+ Document Length: 27958 bytes
11
+
12
+ Concurrency Level: 1
13
+ Time taken for tests: 326.297 seconds
14
+ Complete requests: 30
15
+ Failed requests: 0
16
+ Total transferred: 855851 bytes
17
+ HTML transferred: 838740 bytes
18
+ Requests per second: 0.09 [#/sec] (mean)
19
+ Time per request: 10876.556 [ms] (mean)
20
+ Time per request: 10876.556 [ms] (mean, across all concurrent requests)
21
+ Transfer rate: 2.56 [Kbytes/sec] received
22
+
23
+ Connection Times (ms)
24
+ min mean[+/-sd] median max
25
+ Connect: 55 101 52.8 84 298
26
+ Processing: 8432 10775 3627.0 9669 26365
27
+ Waiting: 8420 10642 3395.2 9532 24849
28
+ Total: 8489 10876 3626.9 9724 26442
29
+
30
+ Percentage of the requests served within a certain time (ms)
31
+ 50% 9724
32
+ 66% 10197
33
+ 75% 10537
34
+ 80% 11088
35
+ 90% 15837
36
+ 95% 18224
37
+ 98% 26442
38
+ 99% 26442
39
+ 100% 26442 (longest request)
40
+ ```
41
+
42
+ ## RIIIF, no cache, kakadu + imagemagick
43
+ ```
44
+ ab -n 30 'https://localhost:3000/iiif/2/bc%2F151%2Fbq%2F1744%2Fbc151bq1744_00_0001.jp2/full/!400,400/0/default.jpg'
45
+ Document Length: 27978 bytes
46
+
47
+ Concurrency Level: 1
48
+ Time taken for tests: 82.646 seconds
49
+ Complete requests: 30
50
+ Failed requests: 0
51
+ Total transferred: 856440 bytes
52
+ HTML transferred: 839340 bytes
53
+ Requests per second: 0.36 [#/sec] (mean)
54
+ Time per request: 2754.880 [ms] (mean)
55
+ Time per request: 2754.880 [ms] (mean, across all concurrent requests)
56
+ Transfer rate: 10.12 [Kbytes/sec] received
57
+
58
+ Connection Times (ms)
59
+ min mean[+/-sd] median max
60
+ Connect: 49 95 65.4 78 398
61
+ Processing: 1991 2660 431.4 2643 4207
62
+ Waiting: 1943 2583 415.3 2565 4033
63
+ Total: 2096 2755 448.5 2721 4395
64
+
65
+ Percentage of the requests served within a certain time (ms)
66
+ 50% 2721
67
+ 66% 2820
68
+ 75% 2944
69
+ 80% 2975
70
+ 90% 3260
71
+ 95% 3486
72
+ 98% 4395
73
+ 99% 4395
74
+ 100% 4395 (longest request)
75
+ ```
@@ -1,9 +1,10 @@
1
+ require 'iiif-image-api'
1
2
  module Riiif
2
3
  class Engine < ::Rails::Engine
3
4
  require 'riiif/rails/routes'
4
5
 
5
6
  # How long to cache the tiles for.
6
- config.cache_duration_in_days = 3
7
+ config.cache_duration = 3.days
7
8
 
8
9
  config.action_dispatch.rescue_responses['Riiif::ImageNotFoundError'] = :not_found
9
10
 
@@ -26,6 +26,9 @@ module Riiif
26
26
  defaults: { format: 'json', model: resource },
27
27
  as: [options[:as], 'info'].compact.join('_')
28
28
 
29
+ match "#{route_prefix}/:id/info.json" => 'riiif/images#info_options',
30
+ via: [:options]
31
+
29
32
  # This doesn't work presently
30
33
  # get "#{route_prefix}/:id", to: redirect("#{route_prefix}/%{id}/info.json")
31
34
  get "#{route_prefix}/:id" => 'riiif/images#redirect', as: [options[:as], 'base'].compact.join('_')
@@ -1,3 +1,3 @@
1
1
  module Riiif
2
- VERSION = '2.0.0.beta2'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.name = 'riiif'
8
8
  spec.version = Riiif::VERSION
9
9
  spec.authors = ['Justin Coyne']
10
- spec.email = ['justin@curationexperts.com']
10
+ spec.email = ['administrator@curationexperts.com']
11
11
  spec.description = 'A IIIF image server'
12
12
  spec.summary = 'A rails engine that support IIIF requests'
13
13
  spec.homepage = 'https://github.com/curationexperts/riiif'
@@ -20,12 +20,12 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency 'railties', '>= 4.2', '<6'
22
22
  spec.add_dependency 'deprecation', '>= 1.0.0'
23
+ spec.add_dependency 'iiif-image-api', '~> 0.1.0'
23
24
  spec.add_development_dependency 'bundler', '~> 1.3'
24
25
  spec.add_development_dependency 'rake'
25
26
  spec.add_development_dependency 'engine_cart', '~> 0.8'
26
27
  spec.add_development_dependency 'rspec-rails'
27
28
  spec.add_development_dependency 'sqlite3'
28
- spec.add_development_dependency 'rubocop', '~> 0.47.1'
29
- spec.add_development_dependency 'rubocop-rspec', '~> 1.13'
29
+ spec.add_development_dependency 'bixby', '~> 1.0.0'
30
30
  spec.add_development_dependency 'coveralls'
31
31
  end
@@ -72,7 +72,7 @@ RSpec.describe Riiif::ImagesController do
72
72
  context 'with a invalid region' do
73
73
  it 'renders 400' do
74
74
  image = double('an image')
75
- allow(image).to receive(:render).and_raise Riiif::InvalidAttributeError
75
+ allow(image).to receive(:render).and_raise IIIF::Image::InvalidAttributeError
76
76
  allow(Riiif::Image).to receive(:new).with('abcd1234').and_return(image)
77
77
  get :show, params: { id: 'abcd1234', action: 'show', region: '`szoW0', size: 'full',
78
78
  rotation: '0', quality: 'default', format: 'jpg' }
@@ -131,6 +131,14 @@ RSpec.describe Riiif::ImagesController do
131
131
  end
132
132
  end
133
133
 
134
+ describe 'info_options' do
135
+ it 'is successful' do
136
+ process :info_options, method: 'OPTIONS', params: { id: 'abcd123', format: 'json' }
137
+ expect(response).to be_successful
138
+ expect(response.headers['Access-Control-Allow-Headers']).to eq 'Authorization'
139
+ end
140
+ end
141
+
134
142
  describe 'info' do
135
143
  context 'the happy path' do
136
144
  let(:image) { double }
@@ -138,7 +146,9 @@ RSpec.describe Riiif::ImagesController do
138
146
 
139
147
  before do
140
148
  allow(Riiif::Image).to receive(:new).with('abcd1234').and_return(image)
141
- allow(image).to receive(:info).and_return(Riiif::ImageInformation.new(6000, 4000))
149
+ allow(image).to(
150
+ receive(:info).and_return(Riiif::ImageInformation.new(width: 6000, height: 4000, format: 'JPEG'))
151
+ )
142
152
  end
143
153
 
144
154
  it 'returns info' do
@@ -148,6 +158,7 @@ RSpec.describe Riiif::ImagesController do
148
158
  '@id' => 'http://test.host/abcd1234',
149
159
  'width' => 6000,
150
160
  'height' => 4000,
161
+ 'format' => 'JPEG',
151
162
  'profile' => ['http://iiif.io/api/image/2/level1.json', 'formats' => %w(jpg png)],
152
163
  'protocol' => 'http://iiif.io/api/image'
153
164
  expect(response.headers['Link']).to eq '<http://iiif.io/api/image/2/level1.json>;rel="profile"'
@@ -163,7 +174,7 @@ RSpec.describe Riiif::ImagesController do
163
174
 
164
175
  before do
165
176
  allow(Riiif::Image).to receive(:new).with('abcd1234').and_return(image)
166
- allow(image).to receive(:info).and_return(Riiif::ImageInformation.new(nil, nil))
177
+ allow(image).to receive(:info).and_return(Riiif::ImageInformation.new(width: nil, height: nil))
167
178
  end
168
179
 
169
180
  it 'returns an error' do
@@ -5,23 +5,15 @@ RSpec.describe Riiif::ImageInformation do
5
5
  subject { info.valid? }
6
6
 
7
7
  context 'with valid dimensions' do
8
- let(:info) { described_class.new(100, 200) }
8
+ let(:info) { described_class.new(width: 100, height: 200) }
9
9
 
10
10
  it { is_expected.to be true }
11
11
  end
12
12
 
13
13
  context 'with nil dimensions' do
14
- let(:info) { described_class.new(nil, nil) }
14
+ let(:info) { described_class.new(width: nil, height: nil) }
15
15
 
16
16
  it { is_expected.to be false }
17
17
  end
18
18
  end
19
-
20
- describe '#[]' do
21
- subject { info[:width] }
22
- let(:info) { described_class.new(100, 200) }
23
- before { allow(Deprecation).to receive(:warn) }
24
-
25
- it { is_expected.to eq 100 }
26
- end
27
19
  end
@@ -30,18 +30,12 @@ RSpec.describe Riiif::Image do
30
30
  it 'is able to override the file used for the Image' do
31
31
  img = described_class.new('some_id', Riiif::File.new(filename))
32
32
  expect(img.id).to eq 'some_id'
33
- expect(img.info).to eq Riiif::ImageInformation.new(800, 400)
34
- end
35
-
36
- describe 'without a format' do
37
- it 'raises an error' do
38
- expect { subject.render('size' => 'full') }.to raise_error ArgumentError
39
- end
33
+ expect(img.info).to eq Riiif::ImageInformation.new(width: 800, height: 400)
40
34
  end
41
35
 
42
36
  describe 'info' do
43
37
  it 'returns the data' do
44
- expect(subject.info).to eq Riiif::ImageInformation.new(800, 400)
38
+ expect(subject.info).to eq Riiif::ImageInformation.new(width: 800, height: 400)
45
39
  end
46
40
  end
47
41
 
@@ -59,7 +53,7 @@ RSpec.describe Riiif::Image do
59
53
  describe 'get info' do
60
54
  subject { described_class.new('Cave_26,_Ajanta') }
61
55
  it 'is easy' do
62
- expect(subject.info).to eq Riiif::ImageInformation.new(600, 390)
56
+ expect(subject.info).to eq Riiif::ImageInformation.new(width: 600, height: 390)
63
57
  end
64
58
  end
65
59
 
@@ -77,7 +71,7 @@ RSpec.describe Riiif::Image do
77
71
  describe '#render' do
78
72
  before do
79
73
  allow(Riiif::CommandRunner).to receive(:execute)
80
- .with("identify -format %hx%w #{filename}[0]").and_return('131x175')
74
+ .with("identify -format '%h %w %m' #{filename}[0]").and_return('131 175 JPEG')
81
75
  end
82
76
 
83
77
  describe 'region' do
@@ -108,7 +102,7 @@ RSpec.describe Riiif::Image do
108
102
 
109
103
  it 'runs the correct imagemagick command' do
110
104
  expect(Riiif::CommandRunner).to receive(:execute)
111
- .with("convert -crop 80%x70+18+13 -strip #{filename} png:-")
105
+ .with("convert -crop 80.0%x70.0+18+13 -strip #{filename} png:-")
112
106
  render
113
107
  end
114
108
  end
@@ -127,7 +121,7 @@ RSpec.describe Riiif::Image do
127
121
  let(:region) { '150x75' }
128
122
 
129
123
  it 'raises an error' do
130
- expect { render }.to raise_error Riiif::InvalidAttributeError
124
+ expect { render }.to raise_error IIIF::Image::InvalidAttributeError
131
125
  end
132
126
  end
133
127
  end
@@ -150,7 +144,7 @@ RSpec.describe Riiif::Image do
150
144
 
151
145
  it 'runs the correct imagemagick command' do
152
146
  expect(Riiif::CommandRunner).to receive(:execute)
153
- .with("convert -resize 50% -strip #{filename} png:-")
147
+ .with("convert -resize 50.0% -strip #{filename} png:-")
154
148
  render
155
149
  end
156
150
  end
@@ -208,7 +202,7 @@ RSpec.describe Riiif::Image do
208
202
  let(:size) { '150x75' }
209
203
 
210
204
  it 'raises an error' do
211
- expect { render }.to raise_error Riiif::InvalidAttributeError
205
+ expect { render }.to raise_error IIIF::Image::InvalidAttributeError
212
206
  end
213
207
  end
214
208
  end
@@ -240,7 +234,7 @@ RSpec.describe Riiif::Image do
240
234
  let(:rotation) { '150x' }
241
235
 
242
236
  it 'raises an error for invalid angle' do
243
- expect { render }.to raise_error Riiif::InvalidAttributeError
237
+ expect { render }.to raise_error IIIF::Image::InvalidAttributeError
244
238
  end
245
239
  end
246
240
  end
@@ -292,7 +286,7 @@ RSpec.describe Riiif::Image do
292
286
  let(:quality) { 'best' }
293
287
 
294
288
  it 'raises an error' do
295
- expect { render }.to raise_error Riiif::InvalidAttributeError
289
+ expect { render }.to raise_error IIIF::Image::InvalidAttributeError
296
290
  end
297
291
  end
298
292
  end