image_svd 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c7595bcaa0980e88306b17c334eb96e97cc9d03
4
- data.tar.gz: e5fd5e67bd0e4ae9138d736640aff240ea6d1e28
3
+ metadata.gz: 7c6be55c37295a68be501d5eb609175825105b52
4
+ data.tar.gz: 33fa7ce261ed05d54123c04ae9bbca9be486b5ba
5
5
  SHA512:
6
- metadata.gz: c5c5d15ea75980e4c0a785695e9c84909b6c8ae6796b18d0ee7484c76961e72cc8ded4c6b6261058058a125b88a3054a290652c35076a97739ce3c03ca7e5eef
7
- data.tar.gz: 065244d4de86a95ea5e8cb6da141f177b23d94480a817f6a6a8b7eaa9e3ec0839284ce2dae41684efb1cb9a4d1ee573b2aced5cbc7abf3926e6c43e246b6cdc8
6
+ metadata.gz: fbca7cbe89abcccbf9f9a916163b1b3ee122fdd3060b821c1a1e237e783f239b85aa63f081bae7d8574337a00f30196cf9ce11e00a706ab964f3d292de28d479
7
+ data.tar.gz: 23083c717ed0fc46950cabd0551c82d38f5ceef2ca0c8dc1a5c3fad0cf1f96f7d3ce1f67c7809aae48b2e301eab9abba4a18fe1ddfaefa5285b4db3b02395dec
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # ImageSvd
1
+ # image_svd
2
2
 
3
- TODO: Write a gem description
3
+ A utility to compress images, with interesting [Richter](https://artsy.net/artist/gerhard-richter)-esque blur effects at high compression.
4
+
5
+ #### [About page](http://ilyakava.tumblr.com/post/86408872127/gerhard-richter-svd-and-me).
6
+
7
+ ![woman with umbrella almost](http://i.imgur.com/tvhJRj5.gif)
8
+
9
+ The above image ([source](http://pixel.nymag.com/imgs/thecut/slideshows/lookbooks/jackie-kennedy/c/jackie-kennedy-085.nocrop.w1800.h1330.jpg)) was chosen for its resemblance to [Woman with Umbrella](http://www.gerhard-richter.com/art/paintings/photo_paintings/detail.php?5499)
4
10
 
5
11
  ## Installation
6
12
 
@@ -18,7 +24,21 @@ Or install it yourself as:
18
24
 
19
25
  ## Usage
20
26
 
21
- TODO: Write usage instructions here
27
+ `image_svd --help`
28
+
29
+ A prerequisite is to have [imagemagick](http://www.imagemagick.org/), and a ruby version 1.9.2 or greater installed.
30
+
31
+ `image_svd -i ~/Downloads/svd_photos/in_RichterfuneralM.jpg -n 10`
32
+
33
+ will create a new image in the current directory using 10 singular values.
34
+
35
+ Be warned! Compression is by no means quick! A 200x300 image takes 15 seconds on my computer. Finding the eigenvalues of a matrix is no easy computational task (O(n!) by brute force, O(n<sup>2.3</sup>) by super fancy approximation).
36
+
37
+ You may also specify a (ruby-style inclusive) range of singular values to output images with, ex:
38
+
39
+ `image_svd -i ~/Downloads/svd_photos/in_RichterfuneralM.jpg -n 1..10`
40
+
41
+ This will lead to 10 images being output, and it is 10 times faster than calling the command 10 times since the most costly step (finding eigenvalues) is only done once.
22
42
 
23
43
  ## Contributing
24
44
 
@@ -7,10 +7,11 @@ module ImageSvd
7
7
  # rubocop:disable MethodLength
8
8
  def run(opts)
9
9
  if opts[:read] == true
10
- app = ImageSvd::ImageMatrix.new_from_svd_savefile(opts[:input_file])
10
+ app = ImageSvd::ImageMatrix.new_from_svd_savefile(opts)
11
11
  app.to_image(opts[:output_name])
12
12
  else
13
- app = ImageSvd::ImageMatrix.new(opts[:num_singular_values])
13
+ opts = process_options(opts)
14
+ app = ImageSvd::ImageMatrix.new(opts[:singular_values])
14
15
  app.read_image(opts[:input_file])
15
16
  if opts[:archive] == true
16
17
  app.save_svd(opts[:output_name])
@@ -19,6 +20,14 @@ module ImageSvd
19
20
  end
20
21
  end
21
22
  end
23
+
24
+ def process_options(opts)
25
+ i, valid_i = [opts[:num_singular_values].to_s, /^\d+\.\.\d+$|^\d+$/]
26
+ fail 'invalid --num-singular-values option' unless i.match valid_i
27
+ vs = i.split('..').map(&:to_i)
28
+ vs = (vs.length == 1 ? vs : ((vs.first)..(vs.last)).to_a)
29
+ opts.merge(singular_values: vs)
30
+ end
22
31
  end
23
32
  # rubocop:enable MethodLength
24
33
 
@@ -45,8 +54,9 @@ module ImageSvd
45
54
  opt :num_singular_values,
46
55
  'The number of singular values to keep for an image. Lower'\
47
56
  ' numbers mean lossier compression; smaller files and more'\
48
- ' distorted images.',
49
- default: 50,
57
+ ' distorted images. You may also provide a range ruby style,'\
58
+ ' (ex: 1..9) in which case many images will be output.',
59
+ default: '50',
50
60
  short: '-n'
51
61
  opt :output_name,
52
62
  'A path/name for an output file (Extension will be ignored).'\
@@ -10,11 +10,13 @@ module ImageSvd
10
10
  class ImageMatrix
11
11
  # rubocop:disable SymbolName
12
12
  # rubocop:disable VariableName
13
- attr_reader :num_singular_values
13
+ attr_reader :singular_values
14
14
  attr_accessor :sigma_vTs, :us, :m, :n
15
15
 
16
- def initialize(num_singular_values)
17
- @num_singular_values = num_singular_values
16
+ def initialize(singular_values)
17
+ fail 'not enough singular values' if singular_values.length.zero?
18
+ @singular_values = singular_values
19
+ @num_singular_values = singular_values.max
18
20
  end
19
21
 
20
22
  def read_image(image_path)
@@ -61,20 +63,24 @@ module ImageSvd
61
63
  end
62
64
  # rubocop:enable MethodLength
63
65
 
64
- def reconstruct_matrix
66
+ def reconstruct_matrix(num_singular_values = nil)
67
+ num_singular_values ||= @num_singular_values
65
68
  zero_matrix = Matrix[*Array.new(@n) { Array.new(@m) { 0 } }]
66
- (0...@num_singular_values).reduce(zero_matrix) do |acc, idx|
69
+ (0...num_singular_values).reduce(zero_matrix) do |acc, idx|
67
70
  acc + (@us[idx] * @sigma_vTs[idx])
68
71
  end.transpose
69
72
  end
70
73
 
71
74
  def to_image(path)
72
- out_path = extension_swap(path, 'jpg')
73
- intermediate = extension_swap(path, 'pgm', '_tmp_outfile')
74
- cleansed_matrix = matrix_to_valid_pixels(reconstruct_matrix)
75
- PNM::Image.new(cleansed_matrix).write(intermediate)
76
- %x(convert #{intermediate} #{out_path})
77
- %x(rm #{intermediate})
75
+ puts 'writing images...' if @singular_values.length > 1
76
+ @singular_values.each do |sv|
77
+ out_path = extension_swap(path, 'jpg', "_#{sv}_svs")
78
+ intermediate = extension_swap(path, 'pgm', '_tmp_outfile')
79
+ cleansed_matrix = matrix_to_valid_pixels(reconstruct_matrix(sv))
80
+ PNM::Image.new(cleansed_matrix).write(intermediate)
81
+ %x(convert #{intermediate} #{out_path})
82
+ %x(rm #{intermediate})
83
+ end
78
84
  true
79
85
  end
80
86
 
@@ -111,9 +117,11 @@ module ImageSvd
111
117
 
112
118
  # @todo error handling code here
113
119
  # @todo serialization is kind of silly as is
114
- def self.new_from_svd_savefile(path)
115
- h = JSON.parse(File.open(path, &:readline))
116
- instance = new(h['sigma_vTs'].length)
120
+ def self.new_from_svd_savefile(opts)
121
+ h = JSON.parse(File.open(opts[:input_file], &:readline))
122
+ svals = [opts[:singular_values], h['sigma_vTs'].size].compact.sort.uniq
123
+ valid_svals = svals.reject { |v| v > h['sigma_vTs'].size }
124
+ instance = new(valid_svals)
117
125
  instance.sigma_vTs = h['sigma_vTs']
118
126
  .map { |arr| Vector[*arr.flatten].covector }
119
127
  instance.us = h['us'].map { |arr| Vector[*arr.flatten] }
@@ -1,4 +1,4 @@
1
1
  # This module holds the gem's code
2
2
  module ImageSvd
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
  end
@@ -15,17 +15,17 @@ describe 'CLI' do
15
15
  num_singular_values: 2,
16
16
  output_name: conv
17
17
  )
18
- i = ImageSvd::ImageMatrix.new(2)
18
+ i = ImageSvd::ImageMatrix.new([2])
19
19
  i.read_image(orig)
20
- i2 = ImageSvd::ImageMatrix.new(2)
21
- i2.read_image(File.new("#{conv}.jpg"))
20
+ i2 = ImageSvd::ImageMatrix.new([2])
21
+ i2.read_image(File.new("#{conv}_2_svs.jpg"))
22
22
  diff_matrix = i.reconstruct_matrix - i2.reconstruct_matrix
23
23
  diff_matrix.to_a.flatten.each do |diff_component|
24
24
  diff_component.abs.should be < 5
25
25
  end
26
26
 
27
27
  # cleanup
28
- %x(rm #{conv}.jpg)
28
+ %x(rm #{conv}_2_svs.jpg)
29
29
  end
30
30
 
31
31
  it 'archives, reads, and converts an image without too great errors' do
@@ -34,25 +34,25 @@ describe 'CLI' do
34
34
  cli.run(
35
35
  input_file: orig,
36
36
  archive: true,
37
- num_singular_values: 2,
37
+ num_singular_values: '2',
38
38
  output_name: conv
39
39
  )
40
40
  # read archive and write and image
41
41
  cli.run(
42
42
  input_file: "#{conv}.svdim",
43
43
  read: true,
44
- output_name: "#{conv}_2"
44
+ output_name: "#{conv}_two"
45
45
  )
46
- i = ImageSvd::ImageMatrix.new(2)
46
+ i = ImageSvd::ImageMatrix.new([2])
47
47
  i.read_image(orig)
48
- i2 = ImageSvd::ImageMatrix.new(2)
49
- i2.read_image(File.new("#{conv}_2.jpg"))
48
+ i2 = ImageSvd::ImageMatrix.new([2])
49
+ i2.read_image(File.new("#{conv}_two_2_svs.jpg"))
50
50
  diff_matrix = i.reconstruct_matrix - i2.reconstruct_matrix
51
51
  diff_matrix.to_a.flatten.each do |diff_component|
52
52
  diff_component.abs.should be < 5
53
53
  end
54
54
  # cleanup
55
- %x(rm #{conv}_2.jpg #{conv}.svdim)
55
+ %x(rm #{conv}_two_2_svs.jpg #{conv}.svdim)
56
56
  end
57
57
  end
58
58
  end
@@ -8,7 +8,7 @@ describe ImageSvd::ImageMatrix do
8
8
  end
9
9
 
10
10
  it 'recovers a 2x3 matrix' do
11
- i = ImageSvd::ImageMatrix.new(2)
11
+ i = ImageSvd::ImageMatrix.new([2])
12
12
  i.decompose(@m)
13
13
  rounded_matrix = Matrix[*i.matrix_to_valid_pixels(i.reconstruct_matrix)]
14
14
  # due to numerical instability, even a 2x3 matrix needs to be rounded
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_svd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Kavalerov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-21 00:00:00.000000000 Z
11
+ date: 2014-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pnm