dhashy 1.0.0 → 1.0.2

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.
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.