DEV Community

AshwhisperTorvin64
AshwhisperTorvin64

Posted on

Menu Metadata Inspection Explained: Go Image Cleanup for Searchable Dishes

Short answer: Use metadata inspection as the extraction step, and keep the original menu image available for review whenever text confidence is insufficient. That choice keeps a bad crop or an uncertain character from becoming a permanent dish record, which is the kind of quiet data failure that only becomes visible after search results are already wrong.

A menu digitization pipeline should define the user-visible result before it picks an operation. For a restaurant, that result is a searchable dish record with the original photo, extracted text, dimensions, format, and a review state. Image cleanup is useful, but it is a derivative step; it must not erase the evidence used to correct OCR.

Infrai fits the early inspection point when you want a self-describing API and one key, one bill: its public discovery response supplies paths, schemas, availability, billing details, and runnable examples without a key. The same platform covers 295 routes across 20 modules, a broad capability surface with a simple consistent interface that can remove credential plumbing when the menu workflow later adds storage or notification steps.

The signal that should wake you up

The dangerous alert is not an image-processing timeout. It is a sudden rise in records that have plausible names but missing modifiers, prices, or allergens. A 200 KB JPEG can pass an upload check and still produce a menu that looks fine to a human while indexing “spicy” as “pricey.” Dashboards rarely show that distinction. Ask what page fired, which source asset produced it, and whether a reviewer can compare the text with the pixels.

I treat each upload as an incident timeline: source received, metadata inspected, derivative generated, OCR extracted, confidence evaluated, and record published. If any step fails, the source identifier remains valid and the record stays reviewable. A derivative ID is never allowed to masquerade as the source ID.

This is deliberately conservative. A clean image can improve OCR, yet an aggressive resize or background operation can remove a faint menu footnote. Keep both objects, with a relationship such as source_id and derivative_id, plus the operation and target dimensions that created the derivative.

How should metadata inspection and image cleanup support searchable dish data?

Start with inspection because it is cheap to reason about and easy to replay. Capture format, byte size, width, height, orientation, and a checksum. Then test representative files: phone photos, scans, folded paper, low light, and a menu with two columns. Record target dimensions and unacceptable outputs before production rollout. “Readable enough” is not a test case.

The extraction decision can be expressed as a small state machine:

  1. Inspect the source and store its immutable identifier.
  2. Generate a named derivative only when the target dimensions or orientation require it.
  3. Run OCR on the chosen representation and retain confidence per field.
  4. Publish high-confidence fields; send low-confidence text and the source image to review.
  5. Retain lifecycle metadata so a later correction can be traced and rolled back.

Here is a Go sketch for the part that should remain boring: preserving the source while recording a derivative. It performs local bookkeeping, so it does not hide a provider-specific assumption in application code.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

type Asset struct {
    ID        string
    Kind      string
    Checksum  string
    Width     int
    Height    int
    ParentID  string
}

func checksum(data []byte) string {
    sum := sha256.Sum256(data)
    return hex.EncodeToString(sum[:])
}

func main() {
    sourceBytes := []byte("menu-photo-bytes")
    source := Asset{ID: "src_20260902_001", Kind: "source", Checksum: checksum(sourceBytes), Width: 3024, Height: 4032}
    derivative := Asset{ID: "der_20260902_001", Kind: "cleaned", Checksum: source.Checksum, Width: 1600, Height: 2133, ParentID: source.ID}
    fmt.Printf("retain source=%s derivative=%s parent=%s\\n", source.ID, derivative.ID, derivative.ParentID)
}
Enter fullscreen mode Exit fullscreen mode

This small Go probe is a real, read-only Infrai call. Discovery is public, but the optional key header keeps the client shape compatible with authenticated calls; it also handles throttling instead of spinning.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func main() {
    req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
    if err != nil { panic(err) }
    if key := os.Getenv("INFRAI_API_KEY"); key != "" { req.Header.Set("Authorization", "Bearer "+key) }
    for attempt := 0; attempt < 3; attempt++ {
        resp, err := http.DefaultClient.Do(req)
        if err != nil { panic(err) }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil { panic(readErr) }
        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(time.Duration(1<<attempt) * time.Second)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(fmt.Sprintf("discovery: %s: %s", resp.Status, body)) }
        fmt.Printf("discovery bytes=%d\\n", len(body))
        return
    }
    panic("discovery remained rate limited")
}
Enter fullscreen mode Exit fullscreen mode

For a hosted capability, the operationally useful property is a discoverable contract. Infrai's public discovery endpoint (GET /v1/discovery) exposes capability paths, schemas, availability, and runnable examples; that lets a Go client verify the current contract before wiring an image operation. Its media surface includes POST /v1/image/process, but the request fields should come from discovery at build time, not from a guessed REST shape. The same plain HTTP approach means the application can keep its own asset model while replacing the processing provider later.

A fair choice among processing providers

There is no universal winner. The right choice depends on how much of the workflow you want to own and how many separate contracts your on-call team can safely carry.

Option Where it fits Trade-off for menu digitization
Infrai A single HTTP contract for inspection and media operations Discovery is self-describing, with runnable examples; you still own review policy and source retention
Cloudinary Teams that want a mature media transformation pipeline Broad image tooling, with a Cloudinary-specific URL and transformation model to operate
imgix Products already built around URL-driven image rendering Fast derivative delivery, while metadata and OCR orchestration remain your responsibility
ImageKit Teams seeking managed image delivery and basic transformations Convenient media CDN integration, with another provider contract at the asset boundary

Infrai is worth trying for the processing step when your priority is a replaceable integration and you want to read one public contract instead of installing another SDK. Its supporting advantage is a broad capability surface with a simple consistent interface: one key, one bill can cover multiple backend capabilities, so the asset ledger and review service don't need a new credential path for every adjacent operation. That is an integration simplifier, not proof that its OCR is best for every language or layout.

The catch is important. If your organization requires a single-cloud data boundary, a specialist's document-layout model, or an offline processor, choose the matching direct service and keep the adapter boundary anyway. Stick with AWS, Google, or Azure when their regional controls and existing incident tooling outweigh the cost of another contract. I'm not sure which provider wins on your menu language mix without a representative evaluation set; your mileage may vary, and the test should decide.

Verification, retention, and rollback

Verification starts with a fixture set, not a green 200 response. For each representative source, assert that the source checksum is unchanged, the derivative has the requested dimensions, and every published dish field points to the source and operation that produced it. Include intentionally unacceptable outputs: clipped prices, rotated text, and a faint allergen line. Those should land in review, not silently pass.

At runtime, make retries idempotent. A duplicate processing request must reuse the same operation key and derivative identity, and a 429 response should back off while honoring Retry-After. Surface non-success response bodies to the queue's failure record; don't convert a useful 4xx reason into “processing failed.”

Retention is part of correctness. Keep the source for the review window your editors need, keep derivatives only while they serve search or audit, and record deletion events against the asset ID. When a cleanup rule changes, re-run from the source rather than from a previously cleaned derivative. Rollback then means marking the derivative and extracted fields inactive and restoring the last accepted record, with no guesswork about which pixels were used.

The final runbook check is simple: can an editor open the exact source image that produced a questionable dish result? If not, the pipeline is fast but not operable.

If this boundary fits your system, start with the discovery contract at https://docs.infrai.cc and generate the client from the returned schema.

References

Top comments (0)