125 lines
4.6 KiB
Go
125 lines
4.6 KiB
Go
|
|
package intelligence
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"math"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"materialanalyzer/internal/model"
|
||
|
|
)
|
||
|
|
|
||
|
|
func buildFingerprint(asset model.MaterialAsset) MaterialFingerprint {
|
||
|
|
variation := variationScore(asset.Semantic.Variation, asset.Visual.Contrast, asset.Texture.EdgeDensity)
|
||
|
|
fp := MaterialFingerprint{
|
||
|
|
SKU: asset.SKU,
|
||
|
|
Brightness: round4(asset.Visual.Brightness),
|
||
|
|
Contrast: round4(asset.Visual.Contrast),
|
||
|
|
Saturation: round4(asset.Visual.Saturation),
|
||
|
|
Variation: round4(variation),
|
||
|
|
TextureEntropy: round4(asset.Texture.Entropy / 8.0),
|
||
|
|
TextureFrequency: round4(asset.Texture.TextureFrequency),
|
||
|
|
Orientation: round4(asset.Texture.PrimaryOrientationDeg),
|
||
|
|
DominantLAB: []float64{
|
||
|
|
round4(asset.Visual.DominantLAB.L),
|
||
|
|
round4(asset.Visual.DominantLAB.A),
|
||
|
|
round4(asset.Visual.DominantLAB.B),
|
||
|
|
},
|
||
|
|
ColorVariance: round4(asset.Canonical.ColorVariance),
|
||
|
|
TextureVariance: round4(asset.Canonical.TextureVariance),
|
||
|
|
MaterialType: firstNonEmpty(asset.Semantic.MaterialType, familyFromSpecific(asset.MaterialSpecific), asset.Category),
|
||
|
|
ColorFamily: asset.Semantic.ColorFamily,
|
||
|
|
SurfaceFinish: asset.Semantic.SurfaceFinish,
|
||
|
|
GlossLevel: asset.Semantic.GlossLevel,
|
||
|
|
}
|
||
|
|
fp.ReadableSignature = fmt.Sprintf("%s %s, %s finish, %s gloss, brightness %.2f, contrast %.2f, entropy %.2f, orientation %.0f",
|
||
|
|
fp.ColorFamily, fp.MaterialType, fp.SurfaceFinish, fp.GlossLevel, fp.Brightness, fp.Contrast, fp.TextureEntropy, fp.Orientation)
|
||
|
|
return fp
|
||
|
|
}
|
||
|
|
|
||
|
|
func buildGroundTruth(asset model.MaterialAsset, fp MaterialFingerprint) GroundTruth {
|
||
|
|
return GroundTruth{
|
||
|
|
SKU: asset.SKU,
|
||
|
|
Brand: asset.Product.Brand,
|
||
|
|
Category: asset.Category,
|
||
|
|
Material: asset.Material,
|
||
|
|
MaterialType: fp.MaterialType,
|
||
|
|
CanonicalColor: firstNonEmpty(asset.Semantic.ColorFamily, asset.Product.ColorTone),
|
||
|
|
SurfaceFinish: asset.Semantic.SurfaceFinish,
|
||
|
|
GlossLevel: asset.Semantic.GlossLevel,
|
||
|
|
GrainType: firstNonEmpty(asset.Semantic.GrainType, asset.Semantic.Grain),
|
||
|
|
VisualStyle: asset.Semantic.VisualStyle,
|
||
|
|
Variation: asset.Semantic.Variation,
|
||
|
|
RenderingConstraints: []string{
|
||
|
|
"Keep material color, species, finish, grain, gloss, and variation identical to the reference texture.",
|
||
|
|
"Use the reference image as material ground truth, not as loose style inspiration.",
|
||
|
|
"Only adjust plank or tile scale when size instructions require it.",
|
||
|
|
},
|
||
|
|
DoNotAlter: []string{"species", "finish", "grain_type", "gloss_level", "color_family", "variation"},
|
||
|
|
Fingerprint: fp,
|
||
|
|
MaterialSpecific: asset.MaterialSpecific,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func buildPromptRecord(asset model.MaterialAsset, gt GroundTruth) PromptRecord {
|
||
|
|
system := "You are a material-constrained image rendering pipeline. Preserve flooring material identity exactly."
|
||
|
|
material := strings.Join([]string{
|
||
|
|
"Material Constraints",
|
||
|
|
"",
|
||
|
|
"SKU: " + asset.SKU,
|
||
|
|
"Material Type: " + gt.MaterialType,
|
||
|
|
"Material: " + firstNonEmpty(asset.Material, gt.MaterialType),
|
||
|
|
"Color Family: " + gt.CanonicalColor,
|
||
|
|
"Surface: " + gt.SurfaceFinish,
|
||
|
|
"Gloss: " + gt.GlossLevel,
|
||
|
|
"Grain: " + gt.GrainType,
|
||
|
|
"Visual Style: " + gt.VisualStyle,
|
||
|
|
"Variation: " + gt.Variation,
|
||
|
|
"",
|
||
|
|
"Keep all material properties identical to the reference texture.",
|
||
|
|
"Do not alter species, finish, grain, gloss, color temperature, or variation.",
|
||
|
|
"The reference texture is ground truth for the floor material.",
|
||
|
|
}, "\n")
|
||
|
|
negative := "Do not reinterpret the material. Do not change wood species, stone type, grain direction, surface finish, gloss level, color family, plank texture, tile texture, or material variation."
|
||
|
|
return PromptRecord{SKU: asset.SKU, SystemPrompt: system, MaterialPrompt: material, NegativePrompt: negative}
|
||
|
|
}
|
||
|
|
|
||
|
|
func variationScore(label string, contrast, edgeDensity float64) float64 {
|
||
|
|
switch strings.ToLower(strings.TrimSpace(label)) {
|
||
|
|
case "high":
|
||
|
|
return math.Max(0.70, math.Min(1, contrast*2.5+edgeDensity*2.0))
|
||
|
|
case "medium":
|
||
|
|
return math.Max(0.35, math.Min(0.75, contrast*2.0+edgeDensity*1.6))
|
||
|
|
case "low":
|
||
|
|
return math.Min(0.35, contrast*1.8+edgeDensity*1.2)
|
||
|
|
default:
|
||
|
|
return math.Min(1, contrast*2.0+edgeDensity*1.5)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func familyFromSpecific(values map[string]interface{}) string {
|
||
|
|
if values == nil {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
if v, ok := values["family"].(string); ok {
|
||
|
|
return v
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
func firstNonEmpty(values ...string) string {
|
||
|
|
for _, value := range values {
|
||
|
|
if strings.TrimSpace(value) != "" {
|
||
|
|
return value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
func round4(v float64) float64 {
|
||
|
|
return math.Round(v*10000) / 10000
|
||
|
|
}
|
||
|
|
|
||
|
|
func round6(v float64) float64 {
|
||
|
|
return math.Round(v*1000000) / 1000000
|
||
|
|
}
|