// presets.go — 7 個系統預設模型(B8)。 // // 設計決策(使用者拍板「簡單版」): // - metadata 寫死成 Go 常數,**不進 DB**。理由:preset 是固定公用資料,不該有 owner // 隔離、不該能被刪、也無 seed 重複問題(多實例 / 重啟結果一致)。 // - 對應的 .nef 檔打包進 visionA-backend image(assets/preset-models/{id}.nef), // download 走 visionA 自己的 storage handler(簡單版不簽 token,preset 公用、本不需授權)。 // // 資料來源:local-tool/server/data/models.json(7 筆 metadata + nef 檔)。對映到 Model // struct 既有欄位;models.json 有但 Model 無對應欄位的 metadata(quantization / license / // version / author / taskType / categories / thumbnail)暫不落地(見 B8 回報)。 // // StorageKey 格式 `models/preset/{id}.nef`:對 download handler 而言只是「preset 識別 + // 對外 URL 檔名」用途,實際檔案由 PresetAssetsDir(api 層)提供,不經 LocalFS storage root。 package model import "time" // presetCreatedAt 是 7 個 preset 的固定建立時間(對齊 models.json 的 createdAt)。 // 寫死成常數讓 List/Get 回應在重啟 / 多實例間穩定(不用 time.Now(),避免每次不同)。 var presetCreatedAt = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) // presetStorageKey 依 preset id 組出對外用的 storage key(檔名穩定、URL 穩定)。 func presetStorageKey(id string) string { return "models/preset/" + id + ".nef" } // presetModels 是寫死的 7 個系統預設模型(公用、無 owner、不可刪)。 // // 欄位對映(models.json → Model): // - name/description/framework 直接對映 // - InputShape:inputSize{width,height} → []int{1, 3, height, width}(NCHW,對齊 // conversion 端與 PG INT[] 既有慣例 [1,3,H,W])。preset 皆 RGB 影像模型,batch=1、channel=3。 // - Classes:labels 直接對映 // - TargetChip:supportedHardware[0](每筆只支援單一晶片) // - FileSize:modelSize // - Source = SourcePreset、OwnerUserID = ""(公用,非任何 user) // - StorageKey = models/preset/{id}.nef(不設 FAAObjectKey;download 走 visionA 自己) var presetModels = []*Model{ { ID: "kl520-yolov5-detection", Name: "YOLOv5 Detection (KL520)", Description: "YOLOv5 object detection model compiled for Kneron KL520. No upsample variant optimized for NPU inference at 640x640 resolution.", StorageKey: presetStorageKey("kl520-yolov5-detection"), FileSize: 7200000, TargetChip: "KL520", InputShape: []int{1, 3, 640, 640}, Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"}, Framework: "NEF", Source: SourcePreset, CreatedAt: presetCreatedAt, UpdatedAt: presetCreatedAt, UploadedAt: &presetCreatedAt, }, { ID: "kl520-fcos-detection", Name: "FCOS Detection (KL520)", Description: "FCOS (Fully Convolutional One-Stage) object detection with DarkNet53s backbone, compiled for KL520. Anchor-free detection at 512x512.", StorageKey: presetStorageKey("kl520-fcos-detection"), FileSize: 8900000, TargetChip: "KL520", InputShape: []int{1, 3, 512, 512}, Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"}, Framework: "NEF", Source: SourcePreset, CreatedAt: presetCreatedAt, UpdatedAt: presetCreatedAt, UploadedAt: &presetCreatedAt, }, { ID: "kl520-ssd-face-detection", Name: "SSD Face Detection (KL520)", Description: "SSD-based face detection with landmark localization, compiled for KL520. Lightweight model suitable for face detection and alignment tasks.", StorageKey: presetStorageKey("kl520-ssd-face-detection"), FileSize: 1000000, TargetChip: "KL520", InputShape: []int{1, 3, 240, 320}, Classes: []string{"face"}, Framework: "NEF", Source: SourcePreset, CreatedAt: presetCreatedAt, UpdatedAt: presetCreatedAt, UploadedAt: &presetCreatedAt, }, { ID: "kl520-tiny-yolov3", Name: "Tiny YOLOv3 (KL520)", Description: "Tiny YOLOv3 object detection model compiled for KL520. Compact and fast model for general-purpose multi-object detection on edge devices.", StorageKey: presetStorageKey("kl520-tiny-yolov3"), FileSize: 9400000, TargetChip: "KL520", InputShape: []int{1, 3, 416, 416}, Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"}, Framework: "NEF", Source: SourcePreset, CreatedAt: presetCreatedAt, UpdatedAt: presetCreatedAt, UploadedAt: &presetCreatedAt, }, { ID: "kl720-yolov5-detection", Name: "YOLOv5 Detection (KL720)", Description: "YOLOv5 object detection model compiled for Kneron KL720. No upsample variant optimized for KL720 NPU inference at 640x640 resolution with USB 3.0 throughput.", StorageKey: presetStorageKey("kl720-yolov5-detection"), FileSize: 10168348, TargetChip: "KL720", InputShape: []int{1, 3, 640, 640}, Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"}, Framework: "NEF", Source: SourcePreset, CreatedAt: presetCreatedAt, UpdatedAt: presetCreatedAt, UploadedAt: &presetCreatedAt, }, { ID: "kl720-resnet18-classification", Name: "ImageNet Classification ResNet18 (KL720)", Description: "ResNet18-based image classification compiled for KL720. Supports 1000 ImageNet categories with fast inference via USB 3.0.", StorageKey: presetStorageKey("kl720-resnet18-classification"), FileSize: 12826804, TargetChip: "KL720", InputShape: []int{1, 3, 224, 224}, Classes: []string{"airplane", "automobile", "bird", "cat", "deer", "dog", "frog", "horse", "ship", "truck"}, Framework: "NEF", Source: SourcePreset, CreatedAt: presetCreatedAt, UpdatedAt: presetCreatedAt, UploadedAt: &presetCreatedAt, }, { ID: "kl720-fcos-detection", Name: "FCOS Detection (KL720)", Description: "FCOS (Fully Convolutional One-Stage) object detection with DarkNet53s backbone, compiled for KL720. Anchor-free detection at 512x512.", StorageKey: presetStorageKey("kl720-fcos-detection"), FileSize: 13004640, TargetChip: "KL720", InputShape: []int{1, 3, 512, 512}, Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"}, Framework: "NEF", Source: SourcePreset, CreatedAt: presetCreatedAt, UpdatedAt: presetCreatedAt, UploadedAt: &presetCreatedAt, }, } // PresetModels 回傳所有系統預設模型的深拷貝(避免呼叫端意外改到共用常數)。 // // 回傳順序固定(與宣告順序一致),讓 List 回應穩定、測試可確定性斷言。 func PresetModels() []*Model { out := make([]*Model, 0, len(presetModels)) for _, m := range presetModels { out = append(out, clonePreset(m)) } return out } // PresetByID 依 id 取單一 preset 的深拷貝;非 preset id 回 (nil, false)。 func PresetByID(id string) (*Model, bool) { for _, m := range presetModels { if m.ID == id { return clonePreset(m), true } } return nil, false } // IsPresetID 判斷 id 是否為系統預設模型 id。 func IsPresetID(id string) bool { _, ok := PresetByID(id) return ok } // clonePreset 深拷貝一筆 preset(含 slice 欄位),避免外部修改污染共用常數。 func clonePreset(m *Model) *Model { cp := *m if m.InputShape != nil { cp.InputShape = append([]int(nil), m.InputShape...) } if m.Classes != nil { cp.Classes = append([]string(nil), m.Classes...) } // UploadedAt 指向共用的 presetCreatedAt;複製一份新指標避免別名共享。 if m.UploadedAt != nil { t := *m.UploadedAt cp.UploadedAt = &t } // preset 是公用模型,語意等同全平台可見(visibility=public)。 // 在此統一標記,preset 宣告區不必逐筆設 Visibility。 cp.Visibility = VisibilityPublic return &cp }