dhashy 1.0.0 → 1.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
- SHA1:
3
- metadata.gz: a7567e25bfc6a4c3d15f84367691faaa0fc83e8b
4
- data.tar.gz: 9924c009112370528804d1f257e2669347782aee
2
+ SHA256:
3
+ metadata.gz: 6fa31671461b8254fadbaf8c8cf8f8e6aee66236781a2981e9b72295b3a3cf7b
4
+ data.tar.gz: 867ed25bb583d62cc4ef510b659fe9f51029cba9f430fdab0bf8c66062181dd1
5
5
  SHA512:
6
- metadata.gz: 3fb31453a4d3f3c38ca182f23d72fd4a6f2f33a4d941489eb7358c8e3de63138ba26ef754e3d795bfc95c1c0e1354b97c73c1f6f013cc5109abd02cdc2ad89ff
7
- data.tar.gz: 1f09b1d33ff31ec8c534dc9ab06ccb22082f51fd66ef38a2ecaefb33c9dae2fa0f033b4ef2fa2e3ffd0ef45a191990f7c02d84df9043418150465e9d37089cdd
6
+ metadata.gz: 9a4af5da1043c7c8ef48afc9bc4d5c0a2a9017904cde5aab2de3781f93c6a74dbf1c09a44c613c26236fb6de88b48d4bba26df251641ff7be70c046fbb14d160
7
+ data.tar.gz: fd6773e276b41aadd428429c827c0f99c49434440477ff557af055a219a9cf7409ce8ec47d4754e9d643d3c959d2dd9bc8cf945760d192bab0647f2e7592105e
data/.gitignore CHANGED
@@ -6,4 +6,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile
9
10
  Gemfile.lock
data/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Dhashy
2
2
 
3
+ Dhashy is a ruby gem implementing the *difference hash* algorithm for perceptual image matching.
4
+
5
+ ## Dependencies
6
+
7
+ Dhashy is tested with ```imagemagick```, version 7. ```Graphicsmagick``` may or may not work.
8
+
9
+ You can choose between ```mini_magick``` or ```vips``` for image processing, via the [```image_processing```](https://github.com/janko-m/image_processing) gem. When using ```vips```.
10
+
3
11
  ## Installation
4
12
 
5
13
  Add this line to your application's Gemfile:
@@ -19,7 +27,7 @@ Or install it yourself as:
19
27
  ## Usage
20
28
 
21
29
  first_image = MiniMagick::Image.open("test/test.jpg")
22
- other_image = MiniMagick::Image.open("test/test.jpg")
30
+ other_image = MiniMagick::Image.open("test/8x8.jpg")
23
31
  first_hash = Dhashy.new(first_image)
24
32
 
25
33
  other_hash = Dhashy.new(other_image)
data/Rakefile CHANGED
@@ -1,20 +1,39 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
-
4
3
  Rake::TestTask.new(:test) do |t|
5
4
  t.libs << "test"
6
5
  t.libs << "lib"
7
6
  t.test_files = FileList["test/**/*_test.rb"]
8
7
  end
9
8
 
10
- task :trials do
9
+ task :tree do |t|
10
+ require '../minimagick/lib/mini_magick'
11
+ require './lib/dhashy'
12
+
13
+ results = []
14
+ (0..9).each do |folder|
15
+ ff = "../chill/data/images/004/%03i/???/1.jpg" % folder
16
+ puts ff
17
+ Pathname.glob(ff).combination(2).to_a.shuffle.each do |first, second|
18
+ puts "#{first} #{second}"
19
+ f1 = Dhashy.new(first)
20
+ f2 = Dhashy.new(second)
21
+ diff = f1 - f2
22
+ results << diff
23
+ puts "difference %2i avg %2i" % [diff, results.sum / results.size]
24
+ end
25
+ end
26
+ end
27
+
28
+ task :trials do |t|
11
29
 
12
30
  require "mini_magick"
31
+ require './lib/dhashy'
13
32
  parameters = {}
14
33
  parameters[:resize] = (0..9).map {|x| 100 - x * 10}
15
34
  parameters[:stretch] = ["100%", "10%", "25%", "50%", "75%", "133%", "200%", "400%", "1000%"]
16
35
  # parameters[:resize_y] = (1..10).map {|x| x * 10}
17
- parameters[:crop] = [false, "90%x90%+5%+5%", "50%x50%+25%+25%","90%x10%-1-1", "10%x100%-10%-10%"]
36
+ #parameters[:crop] = [false, "90%x90%+5%+5%", "50%x50%+25%+25%","90%x10%-1-1", "10%x100%-10%-10%"]
18
37
  parameters[:rotate] = (0..7).map {|x| x * 45}
19
38
  parameters[:flip] = [false, true]
20
39
  parameters[:monochrome] = [false, true]
@@ -24,41 +43,47 @@ task :trials do
24
43
  parameters.each do |key, values|
25
44
  results[key] = Hash[values.map {|value| [value.to_s, []]}]
26
45
  end
27
-
28
- original = Dhashy.new(MiniMagick::Image.open("test/test.jpg"))
46
+ original = Dhashy.new(MiniMagick::Image.open("test/test.jpg"), 128)
29
47
 
30
48
  parameters.each do |change, choices|
31
49
  choices.each do |choice|
32
50
 
33
- settings = Hash[parameters.map {|parameter, values| [parameter, values.first]}]
34
- settings[change] = choice
35
- # settings = Hash[parameters.map {|parameter, values| [parameter, values.sample]}]
36
- other = MiniMagick::Image.open("test/test.jpg")
37
- t = other.get_pixels.size
38
- other.combine_options do |image|
39
- image.resize "#{settings[:resize]}%x#{settings[:stretch]}!"
40
- image.rotate settings[:rotate]
41
- image.flip if settings[:flip]
42
- image.crop settings[:crop] if settings[:crop]
43
- settings[:contrast].times { image.contrast } if settings[:contrast]
44
- image.enhance if settings[:enhance]
45
- image.monochrome if settings[:monochrome]
46
- end
47
- u = other.get_pixels.size
48
- puts "#{t} #{u} #{settings[:resize]}"
49
- diff = original - Dhashy.new(other)
50
- parameters.each do |key, values|
51
- results[key][settings[key].to_s] << diff if key == change
51
+ settings = Hash[parameters.map {|parameter, values| [parameter, values.first]}]
52
+ settings[change] = choice
53
+ other = MiniMagick::Image.open("test/test.jpg")
54
+ t = other.get_pixels.size
55
+ other.combine_options do |image|
56
+ image.resize "#{settings[:resize]}%x#{settings[:stretch]}!"
57
+ image.rotate settings[:rotate]
58
+ image.flip if settings[:flip]
59
+ image.crop settings[:crop] if settings[:crop]
60
+ settings[:contrast].times { image.contrast } if settings[:contrast]
61
+ image.enhance if settings[:enhance]
62
+ image.monochrome if settings[:monochrome]
63
+ end
64
+
65
+ u = other.get_pixels.size
66
+ otherhash = Dhashy.new(other)
67
+ puts "original"
68
+ puts original.display
69
+ puts "other"
70
+ puts otherhash.display
71
+ diff = original - otherhash
72
+ puts "diff: #{diff}"
73
+ parameters.each do |key, values|
74
+ results[key][settings[key].to_s] << diff if key == change
75
+ end
52
76
  end
53
77
  end
54
- end
55
- puts results
78
+
56
79
  results.each do |modification, options|
80
+ puts modification
57
81
  options.keys.sort.each do |option|
58
- results = options[option]
59
- next unless results.size > 0
60
- puts " %2i %4i %s" % [results.sum / results.size, results.size, option]
82
+ thisresult = options[option]
83
+ next unless thisresult.size > 0
84
+ puts " %2i %4i %s" % [thisresult.sum / thisresult.size, thisresult.size, option]
61
85
  end
62
86
  end
63
87
  end
88
+
64
89
  task :default => :test
data/dhashy.gemspec CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |spec|
19
19
  end
20
20
 
21
21
  spec.require_paths = ["lib"]
22
- spec.add_dependency "mini_magick", "~> 4.0"
23
- spec.add_development_dependency "bundler", "~> 1.16.a"
24
- spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_dependency "mini_magick", "~> 4"
23
+ spec.add_development_dependency "bundler", "~> 1.16"
24
+ spec.add_development_dependency "rake", "~> 12.3"
25
25
  spec.add_development_dependency "minitest", "~> 5.0"
26
26
  end
@@ -1,3 +1,3 @@
1
1
  class Dhashy
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.2"
3
3
  end
data/lib/dhashy.rb CHANGED
@@ -1,38 +1,130 @@
1
1
  require "dhashy/version"
2
2
 
3
+ # @author Matthias Winkelmann <m@matthi.coffee>
4
+ # The *difference hash* is a compact representation
5
+ # of an image (i. e. photo) that works well for fuzzy
6
+ # finding of pairs of images that are substantially
7
+ # euqal.
8
+ #
9
+
3
10
  class Dhashy
4
- def initialize(jpg)
5
- return from_array(jpg) if jpg.class == Array
11
+ attr_reader :size
12
+
13
+ def initialize(jpg, size=128)
14
+ self.size = size
15
+
16
+ return from_array(jpg) if jpg.is_a? Array
17
+ jpg = MiniMagick::Image.open(jpg.to_s) if jpg.is_a? Pathname
6
18
  jpg.combine_options do |j|
7
- j.channel_fx "Gray"
8
- j.resize "9x8!"
19
+ j.resize "#{@dimension + 1}x#{@dimension + 1}!"
20
+ end
21
+ @pixels = jpg.get_pixels.map {|row| row.map {|p| ((p[0].to_f * 299) + (p[1] * 587) + (p[2] * 114)).to_f / 1000.0 }}
22
+ @h = from_array(@pixels)
23
+ end
24
+
25
+ # Our budget is spent on two square matrices:
26
+ # one with the differences from row to row
27
+ # the second with differences from column to column
28
+
29
+ def dimension(size)
30
+ Math.sqrt(size / 2).to_i
31
+ end
32
+
33
+ def size=(newsize)
34
+ fail(ArgumentError, "size must be integer, got %s: %s" % [newsize, newsize.class]) unless newsize.is_a?(Numeric)
35
+ fail(ArgumentError, "size must be integer, got %s: %s" % [newsize, newsize.class]) unless newsize.integer?
36
+ fail(ArgumentError, "size must be >= 4") unless newsize >= 4
37
+ if self.dimension(newsize) != Math.sqrt(newsize / 2.0)
38
+ fail(ArgumentError, "sqrt(size / 2) must be integer, is sqrt(%s / 2) / 2 = %s" % [newsize, self.dimension(newsize)])
9
39
  end
10
- @hash = from_array(jpg.get_pixels.map {|x| x.map(&:first) })
40
+ @size = newsize
41
+ @dimension = self.dimension(@size)
11
42
  end
43
+
44
+ # (Hamming-) Distance, or visual difference
45
+ #
46
+ # @param [DHashy] another hash
47
+ # @return [Integer] The Hamming distance (also Manhattan-distance),
48
+ # i. e. the number of different bits
49
+
12
50
  def -(other)
13
- (0..7).map {|x| (0..7).map {|y| (@hash[x][y] - other[x][y]).abs}.sum}.sum
51
+ [0,1].map { |matrix| (0...@dimension).map {|x| (0...@dimension).map {|y| (@h[matrix][x][y] == other[matrix][x][y]) ? 0 : 1 }.sum}.sum}.sum
14
52
  end
53
+
15
54
  def[](index)
16
- @hash[index]
55
+ @h[index]
17
56
  end
18
- def ==(other)
57
+
58
+ # Visual Equality
59
+ #
60
+ # @oaram [DHashy] another hash
61
+ # @param [cutoff] number of bits allowed to differ.
62
+ # Default: @size / 64 (i. e. 2 bits for default size)
63
+ # @return [Boolean]
64
+ def ==(other, cutoff=@size / 64)
19
65
  self - other < 5
20
66
  end
21
67
 
22
- def display
23
- @hash.map {|x| x.join() }.join("\n")
68
+ def display(values = nil)
69
+ values = @h unless values
70
+ [1,0].map do |matrix|
71
+ (0...@dimension).map do |x|
72
+ (0...@dimension).map do |y|
73
+ values[matrix][x][y] ? '*' : '.'
74
+ end.join(" ")
75
+ end.join("\n")
76
+ end.join("\n\n") + "\n"
77
+ end
78
+
79
+ # return [String] The concatenated row- and
80
+ # column-wise matrices, compatible to the
81
+ # python implementation
82
+ # @see https://github.com/Jetsetter/dhash
83
+ def matrix
84
+ [0,1].map do |matrix|
85
+ (0...@dimension).map do |x|
86
+ (0...@dimension).map do |y|
87
+ @h[matrix][x][y] ? '1' : '0'
88
+ end.join
89
+ end.join("\n")
90
+ end.join("\n")
24
91
  end
92
+
25
93
  def to_s
26
- @hash.join()
94
+ [0,1].map do |matrix|
95
+ (0...@dimension).map do |x|
96
+ (0...@dimension).map do |y|
97
+ @h[matrix][x][y] ? 1 : 0
98
+ end.join
99
+ end.join
100
+ end.join
27
101
  end
28
102
 
103
+ # return [Integer] The integer of bit length
104
+ # <size> representing the hash
29
105
  def to_i
30
- (0..7).map {|x| (0..7).map {|y| @hash[x][y] * 2**(x*y)}}.sum
106
+ [0,1].map do |matrix|
107
+ (0...@dimension).map do |x|
108
+ (0...@dimension).map do |y|
109
+ @h[x][y] * 2**(x*y)
110
+ end
111
+ end
112
+ end
113
+ .sum
31
114
  end
32
- private
33
-
34
- def from_array(jpg)
35
- return (0..7).map { |x| (0..7).map { |y| (jpg[x][y] > jpg[x][y+1]) ? 1 : 0 }}
115
+
116
+ private
117
+ def from_array(gray)
118
+ [:rows, :cols].map do |direction|
119
+ (0...@dimension).map do |y|
120
+ (0...@dimension).map do |x|
121
+ if direction == :rows
122
+ gray[x][y] <= gray[x+1][y]
123
+ else
124
+ gray[x][y] <= gray[x][y+1]
125
+ end
126
+ end
127
+ end
36
128
  end
37
-
129
+ end
38
130
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dhashy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Winkelmann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-09 00:00:00.000000000 Z
11
+ date: 2018-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mini_magick
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: '4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.0'
26
+ version: '4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.16.a
33
+ version: '1.16'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.16.a
40
+ version: '1.16'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '12.3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: '12.3'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -105,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
105
  version: '0'
106
106
  requirements: []
107
107
  rubyforge_project:
108
- rubygems_version: 2.6.13
108
+ rubygems_version: 2.7.7
109
109
  signing_key:
110
110
  specification_version: 4
111
111
  summary: Perceptive Image Hashing and Deduplication.