class PH

Constants

IncorrectDimensionsError
NotGreyscaleError

Attributes

pixels[R]
size[R]

Public Class Methods

hash(pixels_2d) click to toggle source
# File lib/ph.rb, line 10
def hash(pixels_2d)
  new(pixels_2d).hash
end
new(pixels_2d) click to toggle source
# File lib/ph.rb, line 19
def initialize(pixels_2d)
  @pixels = pixels_2d
  @size = pixels_2d.size

  raise NotGreyscaleError if pixels_2d.flatten.count != @size**2
  raise IncorrectDimensionsError if !(Math.sqrt(size) % 8).zero?
end
vector(pixels_2d) click to toggle source
# File lib/ph.rb, line 14
def vector(pixels_2d)
  new(pixels_2d).vector
end

Public Instance Methods

hash() click to toggle source
# File lib/ph.rb, line 27
def hash
  # Binary to hex string
  vector.join.to_i(2).to_s(16)
end
vector() click to toggle source
# File lib/ph.rb, line 32
def vector
  @_vector ||= begin
    # Get DCT2D of the pixels
    dct_pixels = dct2d(pixels)
    # Get high frequency corner
    sqrt = Math.sqrt(size).to_i
    coords = Array.new(2, sqrt)
    corner = flat1d(coords, dct_pixels)
    corner_size = corner.length
    # Median values
    med = median(corner)

    result = Array.new(size, 0)

    corner.each.with_index do |f, i|
      # Compare each value to the median
      result[i] = 1 if f > med
    end

    result
  end
end

Private Instance Methods

dct(vector) click to toggle source

1984 Lee DCT implementation

# File lib/ph.rb, line 113
def dct(vector)
  n = vector.size
  return vector if n == 1
  raise StandardError, "Must be nxn" if n.zero? || n.odd?

  half = n / 2
  alpha = []
  beta = []

  Array.new(half).each.with_index do |_, i|
    alpha << (vector[i] + vector[-(i + 1)])
    beta  << (vector[i] - vector[-(i + 1)]) / (Math.cos((i + 0.5) * Math::PI / n) * 2.0)
  end

  alpha = dct(alpha)
  beta = dct(beta)
  result = []

  Array.new(half - 1).each.with_index do |_, i|
    result << alpha[i]
    result << beta[i] + beta[i + 1]
  end

  result << alpha[-1]
  result << beta[-1]

  result
end
dct2d(pixels_2d) click to toggle source

DCT on a bidimensional plane Thank you matlab forum for explaining that it's just transpositions www.mathworks.com/matlabcentral/answers/405088-how-to-implement-dct2-in-matlab-coder

# File lib/ph.rb, line 61
def dct2d(pixels_2d)
  dct_fn = -> (px) { dct(px) }

  pixels_2d
    .map(&dct_fn).transpose
    .map(&dct_fn).transpose
end
flat1d(coords, pixels_2d) click to toggle source

Slices 2D vector into a 1D version

# File lib/ph.rb, line 71
def flat1d(coords, pixels_2d)
  slice = []
  x, y = coords

  Array.new(x) do |i|
    Array.new(y) do |j|
      slice << pixels_2d[i][j]
    end
  end

  slice
end
median(vector) click to toggle source

Quickselect median. rcoh.me/posts/linear-time-median-finding/

# File lib/ph.rb, line 87
def median(vector)
  return nil if vector.empty?

  n = vector.size

  return quickselect(vector, n / 2) if n.odd?
  0.5 * (quickselect(vector, n / 2 - 1) + quickselect(vector, n / 2))
end
quickselect(vector, pos) click to toggle source
# File lib/ph.rb, line 96
def quickselect(vector, pos)
  return vector.first if vector.size == 1 && pos.zero?

  pivot = vector.sample

  lows = vector.select { |i| i < pivot }
  highs = vector.select { |i| i > pivot }
  pivots = vector.select { |i| i == pivot }

  return quickselect(lows, pos) if pos < lows.size
  return pivots.first if pos < pivots.size + lows.size

  quickselect(highs, pos - lows.size - pivots.size)
end