141 lines
3.7 KiB
Go
141 lines
3.7 KiB
Go
package intelligence
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"materialanalyzer/internal/model"
|
|
)
|
|
|
|
type loadedAsset struct {
|
|
Asset model.MaterialAsset
|
|
AssetDir string
|
|
Histogram model.ColorHistogram
|
|
Embeddings []EmbeddingRecord
|
|
}
|
|
|
|
func loadAssets(assetsDir string) ([]loadedAsset, error) {
|
|
entries, err := os.ReadDir(assetsDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read assets dir: %w", err)
|
|
}
|
|
out := make([]loadedAsset, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
assetDir := filepath.Join(assetsDir, entry.Name())
|
|
asset, err := readMaterialAsset(filepath.Join(assetDir, "material.json"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
hist, _ := readHistogram(filepath.Join(assetDir, "histogram.json"))
|
|
embeddings, _ := readEmbeddings(filepath.Join(assetDir, "embedding.bin"), asset.Embeddings)
|
|
out = append(out, loadedAsset{Asset: asset, AssetDir: assetDir, Histogram: hist, Embeddings: embeddings})
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].Asset.SKU < out[j].Asset.SKU })
|
|
return out, nil
|
|
}
|
|
|
|
func readMaterialAsset(path string) (model.MaterialAsset, error) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return model.MaterialAsset{}, err
|
|
}
|
|
defer file.Close()
|
|
var asset model.MaterialAsset
|
|
if err := json.NewDecoder(file).Decode(&asset); err != nil {
|
|
return model.MaterialAsset{}, err
|
|
}
|
|
if strings.TrimSpace(asset.SKU) == "" {
|
|
return model.MaterialAsset{}, fmt.Errorf("asset %s has no SKU", path)
|
|
}
|
|
migrateAssetDefaults(&asset)
|
|
return asset, nil
|
|
}
|
|
|
|
func migrateAssetDefaults(asset *model.MaterialAsset) {
|
|
if asset.Semantic.MaterialType == "" {
|
|
asset.Semantic.MaterialType = firstNonEmpty(familyFromSpecific(asset.MaterialSpecific), asset.Category, asset.Material)
|
|
}
|
|
if asset.Semantic.GrainType == "" {
|
|
asset.Semantic.GrainType = asset.Semantic.Grain
|
|
}
|
|
if asset.Semantic.GlossLevel == "" {
|
|
asset.Semantic.GlossLevel = glossFromFinish(asset.Semantic.SurfaceFinish)
|
|
}
|
|
if asset.Semantic.Confidence == 0 {
|
|
asset.Semantic.Confidence = 0.65
|
|
}
|
|
if asset.AnalyzerVersion == "" {
|
|
asset.AnalyzerVersion = "1.x"
|
|
}
|
|
if asset.FeatureSchema == "" {
|
|
asset.FeatureSchema = "legacy"
|
|
}
|
|
}
|
|
|
|
func glossFromFinish(finish string) string {
|
|
lower := strings.ToLower(finish)
|
|
switch {
|
|
case strings.Contains(lower, "gloss"):
|
|
return "High"
|
|
case strings.Contains(lower, "satin"):
|
|
return "Medium"
|
|
case strings.Contains(lower, "matte"):
|
|
return "Low"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
func readHistogram(path string) (model.ColorHistogram, error) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return model.ColorHistogram{}, err
|
|
}
|
|
defer file.Close()
|
|
var hist model.ColorHistogram
|
|
return hist, json.NewDecoder(file).Decode(&hist)
|
|
}
|
|
|
|
func readEmbeddings(path string, manifest model.EmbeddingManifest) ([]EmbeddingRecord, error) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
data, err := io.ReadAll(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
records := make([]EmbeddingRecord, 0, len(manifest.Vectors))
|
|
for _, vector := range manifest.Vectors {
|
|
start := int(vector.OffsetBytes)
|
|
byteLen := int(vector.ByteLength)
|
|
if byteLen == 0 && vector.Dimensions > 0 {
|
|
byteLen = vector.Dimensions * 4
|
|
}
|
|
end := start + byteLen
|
|
if start < 0 || end > len(data) || byteLen%4 != 0 {
|
|
continue
|
|
}
|
|
values := make([]float32, byteLen/4)
|
|
for i := range values {
|
|
values[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[start+i*4 : start+i*4+4]))
|
|
}
|
|
records = append(records, EmbeddingRecord{
|
|
Name: vector.Name, Model: vector.Model, Provider: vector.Provider,
|
|
Dimensions: len(values), Values: values,
|
|
})
|
|
}
|
|
return records, nil
|
|
}
|