go-places/api/internal/analysis/analysis.go

123 lines
4.1 KiB
Go
Raw Permalink Normal View History

2026-10-07 15:14:50 +02:00
// 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)
}