Class: VisualImageSearchService

Inherits:
Object
  • Object
show all
Defined in:
app/services/visual_image_search_service.rb

Overview

Service for searching images using visual input (URL, uploaded file, or pasted image).
Supports two search modes:

  • pHash: Perceptual hash for finding exact/near duplicates
  • clip: Gemini Embedding 2 for semantic similarity (similar concepts)

Examples:

Search by URL with pHash

VisualImageSearchService.new(url: 'https://example.com/image.jpg', mode: :phash).call

Search by uploaded file with AI embedding

VisualImageSearchService.new(file: uploaded_file, mode: :clip).call

Search by base64 image data

VisualImageSearchService.new(base64: data_url, mode: :clip).call

Search by pre-computed pHash (fast path - client computed hash)

VisualImageSearchService.new(phash: 'a0b1c2d3e4f56789', mode: :phash).call

Defined Under Namespace

Classes: DownloadError, Error, ProcessingError

Constant Summary collapse

SEARCH_MODES =
%i[phash clip].freeze
DEFAULT_LIMIT =
50
MAX_DOWNLOAD_SIZE =

50MB

50 * 1024 * 1024

Instance Method Summary collapse

Constructor Details

#initialize(url: nil, file: nil, base64: nil, phash: nil, mode: :phash, threshold: nil, limit: DEFAULT_LIMIT) ⇒ VisualImageSearchService

Returns a new instance of VisualImageSearchService.

Parameters:

  • url (String) (defaults to: nil)

    URL of image to search

  • file (ActionDispatch::Http::UploadedFile) (defaults to: nil)

    Uploaded file

  • base64 (String) (defaults to: nil)

    Base64-encoded image data (data URL format)

  • phash (String) (defaults to: nil)

    Pre-computed pHash fingerprint (16-char hex string)

  • mode (Symbol) (defaults to: :phash)

    Search mode (:phash or :clip)

  • threshold (Integer, Float) (defaults to: nil)

    Similarity threshold (pHash: 0-20, clip/ai: 0.0-1.0)

  • limit (Integer) (defaults to: DEFAULT_LIMIT)

    Maximum results to return



37
38
39
40
41
42
43
44
45
46
47
# File 'app/services/visual_image_search_service.rb', line 37

def initialize(url: nil, file: nil, base64: nil, phash: nil, mode: :phash, threshold: nil, limit: DEFAULT_LIMIT)
  @url = url
  @file = file
  @base64 = base64
  @phash = phash
  @mode = mode.to_sym
  @threshold = threshold
  @limit = limit.to_i.clamp(1, 100)

  validate_input!
end

Instance Method Details

#callHash

Execute the visual search

Returns:

  • (Hash)

    Search results with :images, :mode, :processing_time, :query_fingerprint/:query_embedding



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'app/services/visual_image_search_service.rb', line 53

def call
  start_time = Time.current

  # Fast path: client provided pre-computed pHash
  if @phash.present?
    results = search_by_precomputed_phash
  else
    tempfile = acquire_image_file
    results = search_with_mode(tempfile)
  end

  {
    success: true,
    images: results[:images],
    mode: @mode,
    processing_time: (Time.current - start_time).round(3),
    query_fingerprint: results[:fingerprint],
    query_embedding_size: results[:embedding]&.size
  }
rescue Error => e
  { success: false, error: e.message, mode: @mode }
ensure
  cleanup_tempfile(tempfile) if defined?(tempfile) && tempfile
end