// Package analysis inspects an uploaded picture and derives badges describing // what can be recognised in it. // // Two implementations are provided. LocalAnalyzer measures the picture's own // pixels and EXIF metadata and turns those measurements into scene and content // badges; it needs no external service and is fully deterministic. RemoteAnalyzer // delegates to an external inference service (a trained object-detection model) // and returns the labels that model predicts. The two can be combined, in which // case model labels take precedence over heuristics of the same name. package analysis import ( "context" "time" "go-places-api/internal/domain" ) // Input is the picture to analyse. type Input struct { // ContentType is the MIME type as supplied by the client. ContentType string // Bytes is the complete encoded picture. Bytes []byte // MaxDimension bounds the longest edge used for pixel measurements. The // picture is downscaled before sampling so that cost does not grow with // megapixels. Zero means use the package default. MaxDimension int // Hints carries scene labels already known about the picture, which lets a // remote analyzer disambiguate using earlier results. Hints []string } // Metadata is the capture information recovered from EXIF, if any. type Metadata struct { // Coordinate is the GPS position from the EXIF GPS IFD. Coordinate *domain.Coordinate // TakenAt is the original capture timestamp. TakenAt *time.Time // Make and Model identify the camera or phone. Make string Model string } // Signals are the measurements taken from the pixels. Every field is // normalised to 0..1 so the badge rules can use a single comparable scale. type Signals struct { // Width and Height are the dimensions of the sampled image. Width int Height int // Brightness is the mean perceptual luminance. Brightness float64 // Saturation is the mean colour saturation. Saturation float64 // Colorfulness is the spread of hue across the image, a stand-in for how // varied the scene is. Colorfulness float64 // EdgeDensity is the fraction of pixels sitting on a strong gradient, which // indicates how much fine detail the picture holds. EdgeDensity float64 // HueWeights holds the fraction of pixels falling into each colour class: // blue, green, warm (red/orange/yellow), purple and achromatic. HueWeights HueWeights // Top, Middle and Bottom hold the same measurements restricted to the // upper, central and lower horizontal bands of the picture. Sky, ground // and water all separate cleanly this way. Top Band Middle Band Bottom Band } // Band is the measurement set for one horizontal third of a picture. type Band struct { Brightness float64 Saturation float64 EdgeDensity float64 HueWeights HueWeights PixelShare float64 PixelCount int } // HueWeights is the distribution of pixels across colour classes. The values // sum to approximately 1 for a fully populated band. type HueWeights struct { Blue float64 Green float64 Warm float64 // Purple covers magenta and violet, which read as neither blue nor warm. Purple float64 // Achromatic is the share of pixels with little or no hue: greys, whites, // blacks and near-neutrals. Achromatic float64 } // Result is everything the pipeline learned about a picture. type Result struct { // Width and Height are the true dimensions of the encoded picture. Width int Height int // Signals are the pixel measurements, retained for debugging and for the // remote analyzer's fallback. Signals Signals // Metadata is what EXIF reported. Metadata Metadata // Badges are the conclusions drawn from the above. Badges []domain.Badge // Note carries a non-fatal warning, such as a degraded analyzer, so the // caller can record why the badge set may be incomplete. Note string } // Analyzer inspects a picture. type Analyzer interface { // Name identifies the analyzer in badge sources. Name() string // Analyze inspects the picture. It returns an error only when the picture // cannot be inspected at all; a picture that yields no badges is a success. Analyze(ctx context.Context, in Input) (*Result, error) }