riiif 2.0.0.beta2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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