zooniverse_data 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 58529b2e92c6d615e55e168226d6532a0490626c
4
+ data.tar.gz: c0698d676a7449cb6d5c3f59e17e71576ddfb50d
5
+ SHA512:
6
+ metadata.gz: 43ae0c3c057c6f129ce6551dda2cc8e43ad16bb7c90e5d0ef07c4079f10d8711358e13204edb8c81506b0fe0fdef515388a488b4287b4580edf9cfac9a0d50df
7
+ data.tar.gz: 66644c38072d4c70376e671fd65b158e2ad92d859a71a706b88ceba4ece0eaff910d6ed3f7580b4671623d79a58a3ec17f2c2576892dd57c9037a1f457f91142
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Michael Parrish
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # ZooniverseData
2
+
3
+ ## This gem is useless unless you have access to [Ouroboros](https://github.com/zooniverse/ouroboros)
4
+
5
+ The primary responsibilities of this gem are to
6
+
7
+ - Create JSON manifests for projects
8
+ - Customize subjects and groups being created in Ouroboros
9
+ - Customize data products used in projects
10
+
11
+ ## Installation
12
+
13
+ Install [ImageMagick](http://www.imagemagick.org)
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'zooniverse_data'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install zooniverse_data
26
+
27
+ ## Usage
28
+
29
+ This gem is required and used from within [Ouroboros](https://github.com/zooniverse/ouroboros)
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( https://github.com/zooniverse/zooniverse_data/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,54 @@
1
+ module ZooniverseData
2
+ module Helpers
3
+ module Base
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :manifest
8
+ attr_accessor :entry
9
+ end
10
+
11
+ def customize(manifest: manifest, entry: entry)
12
+ self.manifest = manifest
13
+ self.entry = entry
14
+
15
+ if entry.subject?
16
+ customize_subject
17
+ elsif entry.group?
18
+ customize_group
19
+ end
20
+ end
21
+
22
+ def customize_subject
23
+
24
+ end
25
+
26
+ def customize_group
27
+
28
+ end
29
+
30
+ def each_location
31
+ new_locations = { }
32
+ entry.location.each_pair do |key, value|
33
+ new_locations[key] = if value.is_a?(Array)
34
+ value.collect do |location|
35
+ _new_location_from yield(key, location)
36
+ end
37
+ else
38
+ _new_location_from yield(key, value)
39
+ end
40
+ end
41
+
42
+ set_location new_locations
43
+ end
44
+
45
+ def set_location(hash)
46
+ entry.update :$set => { location: hash }
47
+ end
48
+
49
+ def _new_location_from(result)
50
+ result.respond_to?(:path) ? result.path : result
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,174 @@
1
+ require 'fastimage'
2
+ require 'ostruct'
3
+
4
+ module ZooniverseData
5
+ module Helpers
6
+ module Images
7
+ class ImageConversionError < StandardError; end
8
+ class ImageOptimizationError < StandardError; end
9
+
10
+ class Image
11
+ attr_accessor :path, :raise_exceptions
12
+
13
+ def initialize(path: path, raise_exceptions: true)
14
+ self.path = path
15
+ self.raise_exceptions = raise_exceptions
16
+ end
17
+
18
+ def size
19
+ width, height = info.size
20
+ OpenStruct.new width: width, height: height
21
+ end
22
+
23
+ def type
24
+ info.type
25
+ end
26
+
27
+ def info
28
+ @info ||= FastImage.new(path, raise_on_failure: raise_exceptions)
29
+ end
30
+
31
+ def optimize
32
+ tap do
33
+ case type
34
+ when :jpeg
35
+ _run_optimization "jpegtran -copy none -optimize -progressive -outfile '#{ tempfile.path }' '#{ path }'"
36
+ when :png, :bmp, :gif, :tiff
37
+ _run_optimization "optipng -strip all -o2 -quiet '#{ path }'"
38
+ end
39
+ end
40
+ end
41
+
42
+ def tempfile
43
+ return @tempfile if @tempfile
44
+ @tempfile = Tempfile.new File.basename path
45
+ end
46
+
47
+ def remove_tempfile
48
+ tempfile.delete
49
+ @tempfile = nil
50
+ end
51
+
52
+ def replace_with_tempfile
53
+ `cp '#{ tempfile.path }' '#{ path }'`
54
+ remove_tempfile
55
+ end
56
+
57
+ def _run_optimization(command)
58
+ success = system command
59
+ raise ImageOptimizationError.new('Image optimization failed') unless success
60
+ replace_with_tempfile if @tempfile
61
+ end
62
+ end
63
+
64
+ class Converter
65
+ attr_accessor :input_image, :output_image, :flags
66
+ attr_accessor :raise_exceptions, :remove_original, :optimize
67
+
68
+ def initialize(path: path, raise_exceptions: true, remove_original: true, optimize: true)
69
+ self.input_image = Image.new path: path, raise_exceptions: raise_exceptions
70
+ self.remove_original = remove_original
71
+ self.optimize = optimize
72
+ self.flags = []
73
+ end
74
+
75
+ def resize(width: width, height: height, type: nil)
76
+ tap do
77
+ resize_type = type ? "#{ type }-resize" : 'resize'
78
+ self.flags << "-#{ resize_type } #{ width }x#{ height }\!"
79
+ end
80
+ end
81
+
82
+ def adaptive_resize(width: width, height: height)
83
+ resize width: width, height: height, type: 'adaptive'
84
+ end
85
+
86
+ def percentage_resize(percentage, type: nil)
87
+ tap do
88
+ resize_type = type ? "#{ type }-resize" : 'resize'
89
+ self.flags << "-#{ resize_type } #{ percentage }% +repage"
90
+ end
91
+ end
92
+
93
+ def adaptive_percentage_resize(percentage)
94
+ percentage_resize percentage, type: 'adaptive'
95
+ end
96
+
97
+ def quality(percentage)
98
+ tap do
99
+ self.flags << "-quality #{ percentage }%"
100
+ end
101
+ end
102
+
103
+ def crop(width: width, height: height, top: top, left: left)
104
+ tap do
105
+ self.flags << "-crop #{ width }x#{ height }+#{ left }+#{ top } +repage"
106
+ end
107
+ end
108
+
109
+ def crop_center(width: width, height: height, top: 0, left: 0)
110
+ tap do
111
+ self.flags << "-gravity Center -crop #{ width }x#{ height }+#{ left }+#{ top } +repage"
112
+ end
113
+ end
114
+
115
+ def negate
116
+ tap do
117
+ self.flags << "-negate"
118
+ end
119
+ end
120
+ alias_method :invert, :negate
121
+
122
+ def write_to(path: nil, prefix: nil, postfix: nil)
123
+ to(path: path, prefix: prefix, postfix: postfix).write
124
+ end
125
+
126
+ def to(path: nil, prefix: nil, postfix: nil)
127
+ raise ImageConversionError.new('Cannot convert an image without an output path') unless path || prefix || postfix
128
+ tap do
129
+ if prefix || postfix
130
+ input_file = input_image.path
131
+ input_path = File.dirname input_file
132
+ input_ext = File.extname input_file
133
+ input_name = File.basename(input_file).sub input_ext, ''
134
+
135
+ input_name = "#{ prefix }_#{ input_name }" if prefix
136
+ input_name = "#{ input_name }_#{ postfix }" if postfix
137
+ path = "#{ input_path }/#{ input_name }#{ input_ext }"
138
+ end
139
+
140
+ self.output_image = Image.new path: path, raise_exceptions: raise_exceptions
141
+ end
142
+ end
143
+
144
+ def write
145
+ raise ImageConversionError.new('Cannot convert an image without an output path') unless output_image
146
+
147
+ output_image.tap do
148
+ success = system "convert #{ input_image.path } #{ flags.join(' ') } #{ output_image.path }"
149
+ raise ImageConversionError.new('Image conversion failed') unless success
150
+ `rm -f '#{ input_image.path }'` if remove_original && input_image.path != output_image.path
151
+ output_image.optimize if optimize
152
+ end
153
+ end
154
+ end
155
+
156
+ def convert_image(path, remove_original: true, optimize: true)
157
+ Converter.new path: path, remove_original: remove_original, optimize: optimize
158
+ end
159
+
160
+ def convert_to_jpeg(path, remove_original: true, optimize: true)
161
+ _simple_convert path, 'jpg', remove_original: remove_original, optimize: optimize
162
+ end
163
+
164
+ def convert_to_png(path, remove_original: true, optimize: true)
165
+ _simple_convert path, 'png', remove_original: remove_original, optimize: optimize
166
+ end
167
+
168
+ def _simple_convert(path, extension, remove_original: true, optimize: true)
169
+ out_path = path.sub(/#{ File.extname(path) }$/, ".#{ extension }")
170
+ convert_image(path, remove_original: remove_original, optimize: optimize).to(path: out_path).write
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,67 @@
1
+ require 'timeout'
2
+ require 'aws-sdk'
3
+
4
+ module ZooniverseData
5
+ module Helpers
6
+ module Transport
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def s3
11
+ @s3 ||= AWS::S3.new
12
+ end
13
+ end
14
+
15
+ def download(from: nil, to: nil, timeout: 60)
16
+ _with_retries(20) do
17
+ `rm -f '#{ to }'`
18
+ Timeout::timeout(timeout) do
19
+ `wget '#{ from }' -t 50 -c -q -O '#{ to }'`
20
+ end
21
+
22
+ raise 'File not downloaded' unless File.exists?(to)
23
+ raise 'File is empty' unless File.new(to).size > 0
24
+ end
25
+ end
26
+
27
+ def upload(from: nil, to: nil, content_type: nil)
28
+ content_type ||= `file -bI '#{ from }'`.chomp.split(';').first
29
+ path = [bucket_path, to].compact.join('/').gsub(/^\//, '').gsub '//', '/'
30
+ obj = bucket.objects[path]
31
+
32
+ _with_retries(20) do
33
+ obj.write file: from, acl: :public_read, content_type: content_type
34
+ raise 'File not uploaded' unless obj.exists?
35
+ end
36
+
37
+ path
38
+ end
39
+
40
+ def bucket
41
+ @bucket ||= self.class.s3.buckets[bucket_name]
42
+ end
43
+
44
+ def bucket_name
45
+ @bucket_name ||= manifest.project.bucket
46
+ end
47
+
48
+ def bucket_path
49
+ @bucket_path ||= manifest.project.bucket_path
50
+ end
51
+
52
+ def _with_retries(retries)
53
+ tries = 0
54
+ begin
55
+ yield
56
+ rescue => e
57
+ tries += 1
58
+ if tries < retries
59
+ retry
60
+ else
61
+ raise e
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ module ZooniverseData
2
+ module Helpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Base
7
+ include Transport
8
+ include Images
9
+ end
10
+
11
+ autoload :Base, 'zooniverse_data/helpers/base'
12
+ autoload :Transport, 'zooniverse_data/helpers/transport'
13
+ autoload :Images, 'zooniverse_data/helpers/images'
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module ZooniverseData
2
+ module Projects
3
+ class Default
4
+ include Helpers
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module ZooniverseData
2
+ module Projects
3
+ class Sunspot
4
+ include Helpers
5
+
6
+ def customize_subject
7
+ standard = convert_to_jpeg entry.location['standard']
8
+ inverted = convert_image(standard.path, remove_original: false).invert.write_to(prefix: 'inverted')
9
+ set_location standard: standard.path, inverted: inverted.path
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module ZooniverseData
2
+ module Projects
3
+ autoload :Default, 'zooniverse_data/projects/default'
4
+ autoload :Sunspot, 'zooniverse_data/projects/sunspot'
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module ZooniverseData
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,28 @@
1
+ require 'zooniverse_data/version'
2
+
3
+ module ZooniverseData
4
+ class << self
5
+ attr_accessor :projects
6
+ end
7
+ self.projects = { }
8
+
9
+ def self.dispatch(manifest: nil, entry: nil)
10
+ klass = self.projects[manifest.project_id]
11
+
12
+ unless klass
13
+ klass_name = manifest.project.name.classify
14
+ klass = if ZooniverseData::Projects.const_defined?(klass_name)
15
+ "ZooniverseData::Projects::#{ klass_name }".constantize
16
+ else
17
+ ZooniverseData::Projects::Default
18
+ end
19
+
20
+ self.projects[manifest.project_id] = klass
21
+ end
22
+
23
+ klass.new.customize manifest: manifest, entry: entry
24
+ end
25
+
26
+ require 'zooniverse_data/helpers'
27
+ require 'zooniverse_data/projects'
28
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'zooniverse_data/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'zooniverse_data'
8
+ spec.version = ZooniverseData::VERSION
9
+ spec.authors = ['Michael Parrish']
10
+ spec.email = ['michael@zooniverse.org']
11
+ spec.summary = 'Zooniverse data library'
12
+ spec.homepage = 'https://github.com/zooniverse/zooniverse_data'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.5'
21
+ spec.add_development_dependency 'rake', '10.1.0'
22
+ spec.add_runtime_dependency 'fastimage', '1.6.0'
23
+ spec.required_ruby_version = '>= 2.0.0'
24
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zooniverse_data
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Parrish
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 10.1.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 10.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: fastimage
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.6.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.6.0
55
+ description:
56
+ email:
57
+ - michael@zooniverse.org
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/zooniverse_data.rb
68
+ - lib/zooniverse_data/helpers.rb
69
+ - lib/zooniverse_data/helpers/base.rb
70
+ - lib/zooniverse_data/helpers/images.rb
71
+ - lib/zooniverse_data/helpers/transport.rb
72
+ - lib/zooniverse_data/projects.rb
73
+ - lib/zooniverse_data/projects/default.rb
74
+ - lib/zooniverse_data/projects/sunspot.rb
75
+ - lib/zooniverse_data/version.rb
76
+ - zooniverse_data.gemspec
77
+ homepage: https://github.com/zooniverse/zooniverse_data
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 2.0.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.2.1
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Zooniverse data library
101
+ test_files: []