initial commit
This commit is contained in:
commit
1253955070
75 changed files with 13972 additions and 0 deletions
99
api/internal/analysis/composite.go
Normal file
99
api/internal/analysis/composite.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package analysis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go-places-api/internal/domain"
|
||||
)
|
||||
|
||||
// Composite runs the local analyzer first and then, when a remote service is
|
||||
// configured, feeds the locally derived scene labels to it as hints. Running
|
||||
// the local pass first is what makes the hints available, and it also means the
|
||||
// picture still gets badges if the remote service is unreachable.
|
||||
type Composite struct {
|
||||
local Analyzer
|
||||
remote Analyzer
|
||||
}
|
||||
|
||||
var _ Analyzer = (*Composite)(nil)
|
||||
|
||||
// NewComposite chains two analyzers. Either may be nil, in which case it is
|
||||
// skipped.
|
||||
func NewComposite(local, remote Analyzer) *Composite {
|
||||
return &Composite{local: local, remote: remote}
|
||||
}
|
||||
|
||||
// Name implements Analyzer.
|
||||
func (c *Composite) Name() string {
|
||||
if c.remote == nil {
|
||||
return SourceLocal
|
||||
}
|
||||
return SourceLocal + "+" + SourceRemote
|
||||
}
|
||||
|
||||
// Analyze measures locally, then augments with model labels when available.
|
||||
//
|
||||
// A failure in the remote service is reported as a non-fatal note rather than
|
||||
// an error: the picture was still understood, just less precisely. Callers can
|
||||
// inspect Note to surface the degradation.
|
||||
func (c *Composite) Analyze(ctx context.Context, in Input) (*Result, error) {
|
||||
var (
|
||||
base *Result
|
||||
local []domain.Badge
|
||||
note string
|
||||
)
|
||||
|
||||
if c.local != nil {
|
||||
res, err := c.local.Analyze(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base = res
|
||||
local = res.Badges
|
||||
} else {
|
||||
base = &Result{}
|
||||
}
|
||||
|
||||
if c.remote != nil {
|
||||
hinted := in
|
||||
hinted.Hints = labelsOf(local)
|
||||
res, err := c.remote.Analyze(ctx, hinted)
|
||||
switch {
|
||||
case err != nil:
|
||||
note = fmt.Sprintf("%s analyzer unavailable: %v", SourceRemote, err)
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
default:
|
||||
base.Width, base.Height = pickDimension(base.Width, res.Width), pickDimension(base.Height, res.Height)
|
||||
base.Badges = Combine(local, res.Badges)
|
||||
}
|
||||
} else {
|
||||
base.Badges = local
|
||||
}
|
||||
|
||||
base.Note = note
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// labelsOf extracts badge labels to use as upstream hints.
|
||||
func labelsOf(badges []domain.Badge) []string {
|
||||
if len(badges) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, 0, len(badges))
|
||||
for _, b := range badges {
|
||||
if b.Confidence >= 0.5 {
|
||||
out = append(out, b.Label)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func pickDimension(current, candidate int) int {
|
||||
if candidate > 0 {
|
||||
return candidate
|
||||
}
|
||||
return current
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue