Files
observer/server/biz/dao/dataset_image.go
T
2026-09-02 11:18:04 +08:00

206 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dao
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"observer-server/biz/consts"
"observer-server/biz/model/entity"
"observer-server/common"
)
// DatasetImage 数据集图片表 DAO:文件名唯一(防重名),按数据集查询;表小不做缓存。
type datasetImageDao struct{}
var DatasetImage = &datasetImageDao{}
func init() {
ctx := context.Background()
_, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS dataset_image (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dataset_id INTEGER NOT NULL,
filename TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'manual',
animal_count INTEGER NOT NULL DEFAULT 0,
labels_json TEXT,
created_at TEXT NOT NULL,
UNIQUE (dataset_id, filename)
)`)
if err != nil {
panic(err)
}
// 存量库迁移:标注列缺失时追加(新库建表已含列,幂等跳过);candidates_json 已随 v10 删除
// 注意 EnsureColumn 的 ddl 参数须自带列名(拼接为 ALTER TABLE ADD COLUMN <ddl>
common.EnsureColumn(ctx, consts.TableDatasetImage, "labels_json", "labels_json TEXT")
common.EnsureColumn(ctx, consts.TableDatasetImage, "animal_count", "animal_count INTEGER NOT NULL DEFAULT 0")
// 存量库迁移:删除 prompt 列(生成提示词不再存储,2026-09-02 决策)
cols, err := g.DB().GetAll(ctx, "PRAGMA table_info(dataset_image)")
if err != nil {
panic(err)
}
for _, col := range cols {
if gconv.String(col["name"]) == "prompt" {
if _, err := g.DB().Exec(ctx, "ALTER TABLE dataset_image DROP COLUMN prompt"); err != nil {
panic(err)
}
g.Log().Warningf(ctx, "存量表 dataset_image 已迁移:删除 prompt 列")
break
}
}
}
// Insert 插入图片记录
func (d *datasetImageDao) Insert(ctx context.Context, m *entity.DatasetImage) (int64, error) {
res, err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).Data(g.Map{
"dataset_id": m.DatasetId,
"filename": m.Filename,
"source": m.Source,
"animal_count": m.AnimalCount,
"created_at": m.CreatedAt,
}).Insert()
if err != nil {
return 0, err
}
return res.LastInsertId()
}
// GetById 按主键取图片记录
func (d *datasetImageDao) GetById(ctx context.Context, id int64) (*entity.DatasetImage, error) {
var one *entity.DatasetImage
err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).Where("id", id).Scan(&one)
if err != nil {
return nil, err
}
return one, nil
}
// ListByDataset 某数据集全部图片(按文件名编号倒序,详情页逐行展示用)。
// 文件名形如 <前缀>_<编号>.jpg(无前缀为时间戳数字名),须提取下划线后的数字按数值倒序:
// 字符串序会把 pheasant_101.jpg 排在 pheasant_2.jpg 之前('1'<'9'),导致最大编号落到底部
func (d *datasetImageDao) ListByDataset(ctx context.Context, datasetId int64) ([]*entity.DatasetImage, error) {
var list []*entity.DatasetImage
err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).
Where("dataset_id", datasetId).
Order("CAST(substr(filename, instr(filename, '_') + 1) AS INTEGER) DESC").Scan(&list)
if err != nil {
if common.IsNoRows(err) {
return []*entity.DatasetImage{}, nil
}
return nil, err
}
return list, nil
}
// ListUnlabeledByDataset 未标注图片(labels_json 为空/null/'[]';自动补标轮次用)
func (d *datasetImageDao) ListUnlabeledByDataset(ctx context.Context, datasetId int64) ([]*entity.DatasetImage, error) {
var list []*entity.DatasetImage
err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).
Where("dataset_id", datasetId).
Where("labels_json IS NULL OR labels_json = '' OR labels_json = '[]'").
OrderAsc("id").Scan(&list)
if err != nil {
if common.IsNoRows(err) {
return []*entity.DatasetImage{}, nil
}
return nil, err
}
return list, nil
}
// GetByIds 按 id 批量取(删除图片定位文件用)
func (d *datasetImageDao) GetByIds(ctx context.Context, ids []int64) ([]*entity.DatasetImage, error) {
var list []*entity.DatasetImage
err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).WhereIn("id", ids).Scan(&list)
if err != nil {
if common.IsNoRows(err) {
return []*entity.DatasetImage{}, nil
}
return nil, err
}
return list, nil
}
// GetByFilename 按文件名取(图片访问校验归属)
func (d *datasetImageDao) GetByFilename(ctx context.Context, datasetId int64, filename string) (*entity.DatasetImage, error) {
var e entity.DatasetImage
err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).
Where("dataset_id", datasetId).Where("filename", filename).Scan(&e)
if err != nil {
if common.IsNoRows(err) {
return nil, nil
}
return nil, err
}
return &e, nil
}
// CountByDataset 某数据集图片数
func (d *datasetImageDao) CountByDataset(ctx context.Context, datasetId int64) (int64, error) {
n, err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).Where("dataset_id", datasetId).Count()
return int64(n), err
}
// UpdateLabels 覆写标注 JSON(整图粒度;空串=清空标注;AI 自动标注与人工保存共用)
func (d *datasetImageDao) UpdateLabels(ctx context.Context, id int64, labelsJson string) error {
_, err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).Where("id", id).
Data(g.Map{"labels_json": labelsJson}).Update()
return err
}
// CountLabeledByDataset 某数据集已标注图片数(labels_json 非空数组的图片行数)
func (d *datasetImageDao) CountLabeledByDataset(ctx context.Context, datasetId int64) (int64, error) {
n, err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).
Where("dataset_id", datasetId).
Where("labels_json IS NOT NULL AND labels_json != '' AND labels_json != '[]'").
Count()
return int64(n), err
}
// CountStat 某数据集图片数/已标注数(列表实时统计:冗余计数列会漂移,展示一律以此为准)
type CountStat struct {
Count int64
Labeled int64
}
// CountStatByDatasets 多数据集图片数/已标注数(一次 GROUP BY 查询;IN 按 ≤100 分批)
func (d *datasetImageDao) CountStatByDatasets(ctx context.Context, datasetIds []int64) (map[int64]CountStat, error) {
stat := make(map[int64]CountStat)
for start := 0; start < len(datasetIds); start += 100 {
end := start + 100
if end > len(datasetIds) {
end = len(datasetIds)
}
var rows []struct {
DatasetId int64 `orm:"dataset_id"`
Cnt int64 `orm:"cnt"`
Labeled int64 `orm:"labeled_cnt"`
}
err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).
WhereIn("dataset_id", datasetIds[start:end]).
Fields("dataset_id, COUNT(*) AS cnt, SUM(CASE WHEN labels_json IS NOT NULL AND labels_json != '' AND labels_json != '[]' THEN 1 ELSE 0 END) AS labeled_cnt").
Group("dataset_id").
Scan(&rows)
if err != nil {
return nil, err
}
for _, r := range rows {
stat[r.DatasetId] = CountStat{Count: r.Cnt, Labeled: r.Labeled}
}
}
return stat, nil
}
// DeleteByIds 按 id 批量删除(标注随行删除)
func (d *datasetImageDao) DeleteByIds(ctx context.Context, ids []int64) error {
_, err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).WhereIn("id", ids).Delete()
return err
}
// DeleteByDataset 删除某数据集全部图片记录(删数据集联动)
func (d *datasetImageDao) DeleteByDataset(ctx context.Context, datasetId int64) error {
_, err := g.DB().Model(consts.TableDatasetImage).Ctx(ctx).Where("dataset_id", datasetId).Delete()
return err
}