pdq

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 14, 2026 License: MIT Imports: 5 Imported by: 0

README

PDQ

A Go implementation of the PDQ perceptual hashing algorithm developed by Meta for detecting visually similar images.

PDQ produces a 256-bit fingerprint from an image. Two images that look similar to a human will have hashes with a low Hamming distance, despite being cropped, compressed, or edited in a minor way.

Installation

go get github.com/MatthewSH/pdq

Quick Start

package main

import (
    "fmt"
    "image"
    _ "image/jpeg"
    _ "image/png"
    "os"

    "github.com/MatthewSH/pdq"
)

func main() {
    f, err := os.Open("photo.jpg")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    img, _, err := image.Decode(f)
    if err != nil {
        panic(err)
    }

    result, err := pdq.Hash(img)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Hash:    %s\n", result.Hash)
    fmt.Printf("Quality: %d\n", result.Quality)
}

Comparing Images

PDQ hashes are compared using Hamming distance. Two images are considered a match when their distance is at or below DefaultMatchThreshold (31, recommended by Meta's reference implementation).

r1, _ := pdq.Hash(img1)
r2, _ := pdq.Hash(img2)

if r1.Hash.Distance(r2.Hash) <= pdq.DefaultMatchThreshold {
    fmt.Println("Images are visually similar")
}

Quality Scores

The Result.Quality field is a 0–100 score reflecting how much gradient information the image contained. Very small or nearly featureless images will score near zero and produce unreliable hashes. Discard results below DefaultQualityThreshold (50, recommended by Meta's reference implementation).

result, err := pdq.Hash(img)
if err != nil || result.Quality < pdq.DefaultQualityThreshold {
    // hash is not reliable
}

Dihedral Hashes

Result.Dihedrals contains all 8 rotational and reflective variants of the hash (original, rotate 90/180/270, flip-x, flip-y, flip +diagonal, flip −diagonal). These are useful for rotation-invariant matching.

result, _ := pdq.Hash(img)
for i, h := range result.Dihedrals {
    fmt.Printf("dihedral[%d]: %v\n", i, h)
}

API

Hash(img image.Image) (Result, error)

Computes the PDQ perceptual hash of an image. Returns a Result containing the primary hash, quality score, and all 8 dihedral variant hashes.

Result
type Result struct {
    Hash      Hash256       // Primary perceptual hash
    Quality   int           // Quality score (0–100)
    Dihedrals [8][16]uint16 // All 8 dihedral variant hashes
}
Hash256

A 256-bit PDQ perceptual hash stored as 32 bytes.

type Hash256 [32]byte

func (h Hash256) String() string                  // 64-char lowercase hex
func (h Hash256) Bytes() []byte                   // Raw bytes
func (h Hash256) IsZero() bool                    // True if hash is empty
func (h Hash256) Distance(other Hash256) int      // Hamming distance (0–256)
Constants
Constant Value Description
DefaultMatchThreshold 31 Max Hamming distance to consider images a match
DefaultQualityThreshold 50 Min quality score for a reliable hash

Running Tests

Unit tests run without any fixtures:

go test

Integration tests compare output against known-good hashes from Meta's reference data. Download the fixtures first:

go run ./internal/cmd/fetch-testdata
go test -tags integration

How It Works

The hashing pipeline follows the PDQ specification:

  1. Resize – the image is downscaled to at most 512 × 512 using bilinear interpolation.
  2. Luminance - RGB pixels are converted to grayscale using standard luma weights.
  3. Jarosz filter – a separable box filter smooths and decimates the luma plane to 64 × 64.
  4. DCT - a 2D Discrete Cosine Transform reduces the 64 × 64 matrix to a 16 × 16 block of low-frequency coefficients.
  5. Quantize – each coefficient is thresholded against the median, producing the final 256-bit hash.

Quality is derived from the gradient energy of the 64 × 64 filtered image before the DCT step.

License

This PDQ implementation is licensed under the MIT license.

Documentation

Index

Constants

View Source
const (
	// DefaultMatchThreshold is the recommended Hamming distance threshold for
	// considering two PDQ hashes a match. Per the PDQ spec README:
	// "Distance Threshold to consider two hashes similar/matching: <=31"
	DefaultMatchThreshold = 31

	// DefaultQualityThreshold is the minimum quality score for a hash to be
	// considered reliable. Per the PDQ spec README:
	// "Quality Threshold where we recommend discarding hashes: <=49"
	DefaultQualityThreshold = 50
)

Variables

View Source
var (
	// ErrNilImage is returned when a nil image.Image is passed to Hash.
	ErrNilImage = errors.New("pdq: image must not be nil")
)

Functions

This section is empty.

Types

type Hash256

type Hash256 [internal.HashSize]byte

Hash256 is a 256-bit PDQ perceptual hash stored as 32 bytes.

func (Hash256) Bytes

func (h Hash256) Bytes() []byte

func (Hash256) Distance

func (h Hash256) Distance(otherHash Hash256) int

Distance calculates the Hamming distance between the current 256-bit hash and another 256-bit hash.

func (Hash256) IsZero

func (h Hash256) IsZero() bool

func (Hash256) String

func (h Hash256) String() string

String returns the hash as a 64-character lowercase hex string.

type Result

type Result struct {
	Hash      Hash256
	Quality   int
	Dihedrals [8][16]uint16
}

func Hash

func Hash(img image.Image) (Result, error)

Hash computes the PDQ perceptual hash of an image. Returns the primary hash, quality score (0–100), and all 8 dihedral hashes.

func (Result) String

func (r Result) String() string

Directories

Path Synopsis
cmd command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL