ph 0.0.1 → 0.1.0
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 +4 -4
- data/.gitignore +1 -0
- data/Makefile +3 -0
- data/README.md +60 -0
- data/lib/ph.rb +18 -2
- data/ph.gemspec +1 -1
- data/spec/ph_spec.rb +22 -20
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24b368462e37a0884ab35c35b3fee219c3463e051c4b51a543678412938c46e2
|
4
|
+
data.tar.gz: 6e91f21c7e6c1d59259b5eb45e67b4c6e6716346d2c1389ca6a221d80fe216b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afb8a6f5f61b52a041934f1bb71764b6067665f8f0b505f8772d6a3a11d25565e313d1cb03fb3ff77ab436be379353275fe163241238f0b145c94eb074334e07
|
7
|
+
data.tar.gz: e2eeeb235206cc887f3f303702faf11443f8b3e5127fdcd77c21fa6477847bf29ee02ad24a2c64133e74d784f0a5252288ab896a85a4e155a506c14d8e1b7918
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Makefile
CHANGED
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# PH
|
2
|
+
_Perceptual Hashing_
|
3
|
+
|
4
|
+
## Install
|
5
|
+
|
6
|
+
```bash
|
7
|
+
gem install ph
|
8
|
+
```
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
`PH` Generates a perceptual hash from an array of pixels.
|
13
|
+
The way you get that pixel data is up to you. Different techniques can shield different values.
|
14
|
+
Hashes exist on a similar space but different reads could yield different (subtle) hashes.
|
15
|
+
|
16
|
+
### Distance
|
17
|
+
|
18
|
+
Since hashes exist on a metric space you can measure how far a hash is from another.
|
19
|
+
You can use the hamming gem for calculations if needed
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Hamming.distance(hash_a, hash_b)
|
23
|
+
|
24
|
+
# You can also transform hashes based on your storage:
|
25
|
+
Hamming.vector_to_hash(hash)
|
26
|
+
Hamming.hash_to_vector(vector)
|
27
|
+
```
|
28
|
+
|
29
|
+
### Vips
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
size = 64
|
33
|
+
img = Vips::Image.new_from_file(file)
|
34
|
+
width, height = img.width, img.height
|
35
|
+
w_scale, v_scale = size.fdiv(width), size.fdiv(height)
|
36
|
+
|
37
|
+
img = img
|
38
|
+
.resize(w_scale, vscale: v_scale)
|
39
|
+
.colourspace(:grey16)
|
40
|
+
|
41
|
+
pixels_2d = img.to_a.map(&:flatten)
|
42
|
+
|
43
|
+
PH.hash(pixels_2d)
|
44
|
+
# => "859091ce633aaebb"
|
45
|
+
```
|
46
|
+
|
47
|
+
### RMagick
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
img = Magick::ImageList.new(file)
|
51
|
+
size = 64
|
52
|
+
img = img
|
53
|
+
.scale(size, size)
|
54
|
+
.dispatch(0, 0, size, size, "I")
|
55
|
+
.each_slice(size)
|
56
|
+
.to_a
|
57
|
+
|
58
|
+
PH.new(img).vector
|
59
|
+
# => [1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1]
|
60
|
+
```
|
data/lib/ph.rb
CHANGED
@@ -3,9 +3,25 @@
|
|
3
3
|
class PH
|
4
4
|
attr_reader :pixels, :size
|
5
5
|
|
6
|
-
|
6
|
+
NotGreyscaleError = Class.new(StandardError)
|
7
|
+
IncorrectDimensionsError = Class.new(StandardError)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def hash(pixels_2d)
|
11
|
+
new(pixels_2d).hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def vector(pixels_2d)
|
15
|
+
new(pixels_2d).vector
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(pixels_2d)
|
7
20
|
@pixels = pixels_2d
|
8
|
-
@size = size
|
21
|
+
@size = pixels_2d.size
|
22
|
+
|
23
|
+
raise NotGreyscaleError if pixels_2d.flatten.count != @size**2
|
24
|
+
raise IncorrectDimensionsError if !(Math.sqrt(size) % 8).zero?
|
9
25
|
end
|
10
26
|
|
11
27
|
def hash
|
data/ph.gemspec
CHANGED
data/spec/ph_spec.rb
CHANGED
@@ -4,30 +4,32 @@ describe PH do
|
|
4
4
|
let(:file) { "spec/image.jpg" }
|
5
5
|
let(:size) { 64 }
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
describe "hashing an image" do
|
8
|
+
before do
|
9
|
+
img = Vips::Image.new_from_file(file)
|
10
|
+
width, height = img.width, img.height
|
11
|
+
w_scale, v_scale = size.fdiv(width), size.fdiv(height)
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# Rescale to 64x64 and greyscale
|
14
|
+
img = img.resize(w_scale, vscale: v_scale).colourspace(:grey16)
|
15
|
+
# Ensure a 2D plane
|
16
|
+
@pixels_2d = img.to_a.map(&:flatten)
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
+
subject { PH.new(@pixels_2d) }
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
it "#hash" do
|
22
|
+
assert_equal "859091ce633aaebb", subject.hash
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
it "#vector" do
|
26
|
+
vector = [
|
27
|
+
1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1,
|
28
|
+
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0,
|
29
|
+
1, 1
|
30
|
+
]
|
30
31
|
|
31
|
-
|
32
|
+
assert_equal vector, subject.vector
|
33
|
+
end
|
32
34
|
end
|
33
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- elcuervo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04-
|
11
|
+
date: 2021-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: vips
|
@@ -31,7 +31,9 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
+
- ".gitignore"
|
34
35
|
- Makefile
|
36
|
+
- README.md
|
35
37
|
- lib/ph.rb
|
36
38
|
- ph.gemspec
|
37
39
|
- spec/image.jpg
|