83 lines
2.7 KiB
Go
83 lines
2.7 KiB
Go
|
|
package intelligence
|
||
|
|
|
||
|
|
import (
|
||
|
|
"sort"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func buildStatistics(assets []loadedAsset, fingerprints map[string]MaterialFingerprint) StatisticsCenter {
|
||
|
|
stats := StatisticsCenter{
|
||
|
|
Version: "3.0",
|
||
|
|
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
||
|
|
TotalAssets: len(assets),
|
||
|
|
MaterialDistribution: map[string]int{},
|
||
|
|
MaterialTypeDistribution: map[string]int{},
|
||
|
|
SpeciesDistribution: map[string]int{},
|
||
|
|
ColorDistribution: map[string]int{},
|
||
|
|
GlossDistribution: map[string]int{},
|
||
|
|
BrightnessDistribution: make([]float64, 10),
|
||
|
|
}
|
||
|
|
if len(assets) == 0 {
|
||
|
|
return stats
|
||
|
|
}
|
||
|
|
var sumBrightness, sumContrast, sumSaturation, sumEntropy float64
|
||
|
|
for _, item := range assets {
|
||
|
|
asset := item.Asset
|
||
|
|
fp := fingerprints[asset.SKU]
|
||
|
|
stats.MaterialDistribution[firstNonEmpty(asset.Material, "unknown")]++
|
||
|
|
stats.MaterialTypeDistribution[firstNonEmpty(fp.MaterialType, "unknown")]++
|
||
|
|
stats.SpeciesDistribution[firstNonEmpty(asset.Semantic.GrainType, asset.Semantic.Grain, "unknown")]++
|
||
|
|
stats.ColorDistribution[firstNonEmpty(fp.ColorFamily, "unknown")]++
|
||
|
|
stats.GlossDistribution[firstNonEmpty(fp.GlossLevel, "unknown")]++
|
||
|
|
bin := int(fp.Brightness * float64(len(stats.BrightnessDistribution)))
|
||
|
|
if bin >= len(stats.BrightnessDistribution) {
|
||
|
|
bin = len(stats.BrightnessDistribution) - 1
|
||
|
|
}
|
||
|
|
if bin < 0 {
|
||
|
|
bin = 0
|
||
|
|
}
|
||
|
|
stats.BrightnessDistribution[bin]++
|
||
|
|
sumBrightness += fp.Brightness
|
||
|
|
sumContrast += fp.Contrast
|
||
|
|
sumSaturation += fp.Saturation
|
||
|
|
sumEntropy += fp.TextureEntropy
|
||
|
|
stats.EmbeddingPCA = append(stats.EmbeddingPCA, embeddingProjection(item, fp))
|
||
|
|
}
|
||
|
|
total := float64(len(assets))
|
||
|
|
for i := range stats.BrightnessDistribution {
|
||
|
|
stats.BrightnessDistribution[i] = round6(stats.BrightnessDistribution[i] / total)
|
||
|
|
}
|
||
|
|
stats.AverageBrightness = round4(sumBrightness / total)
|
||
|
|
stats.AverageContrast = round4(sumContrast / total)
|
||
|
|
stats.AverageSaturation = round4(sumSaturation / total)
|
||
|
|
stats.AverageTextureEntropy = round4(sumEntropy / total)
|
||
|
|
stats.ClusterStatistics = buildClusterStats(stats.MaterialTypeDistribution)
|
||
|
|
return stats
|
||
|
|
}
|
||
|
|
|
||
|
|
func embeddingProjection(item loadedAsset, fp MaterialFingerprint) PCAPoint {
|
||
|
|
vec, _ := comparableVector(item, fp)
|
||
|
|
x, y := 0.0, 0.0
|
||
|
|
if len(vec) > 0 {
|
||
|
|
x = vec[0]
|
||
|
|
}
|
||
|
|
if len(vec) > 1 {
|
||
|
|
y = vec[1]
|
||
|
|
}
|
||
|
|
return PCAPoint{SKU: item.Asset.SKU, X: round6(x), Y: round6(y)}
|
||
|
|
}
|
||
|
|
|
||
|
|
func buildClusterStats(dist map[string]int) []ClusterStat {
|
||
|
|
out := make([]ClusterStat, 0, len(dist))
|
||
|
|
for name, count := range dist {
|
||
|
|
out = append(out, ClusterStat{Name: name, Count: count})
|
||
|
|
}
|
||
|
|
sort.Slice(out, func(i, j int) bool {
|
||
|
|
if out[i].Count != out[j].Count {
|
||
|
|
return out[i].Count > out[j].Count
|
||
|
|
}
|
||
|
|
return out[i].Name < out[j].Name
|
||
|
|
})
|
||
|
|
return out
|
||
|
|
}
|