feat: improved visibility of dataset and improved speed for expand import zip file

This commit is contained in:
Andre Henriques 2024-04-09 17:28:59 +01:00
parent 00369640a8
commit 143ad3b02b
14 changed files with 727 additions and 464 deletions

11
go.mod
View File

@ -8,15 +8,20 @@ require (
github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99 github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
golang.org/x/crypto v0.18.0 golang.org/x/crypto v0.19.0
) )
require ( require (
github.com/BurntSushi/toml v1.3.2 // indirect github.com/BurntSushi/toml v1.3.2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
@ -24,6 +29,8 @@ require (
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.6 // indirect github.com/rivo/uniseg v0.4.6 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect google.golang.org/protobuf v1.32.0 // indirect
) )

18
go.sum
View File

@ -10,6 +10,8 @@ github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7
github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k= github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k=
github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw=
github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e h1:9+2AEFZymTi25FIIcDwuzcOPH04z9+fV6XeLiGORPDI= github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e h1:9+2AEFZymTi25FIIcDwuzcOPH04z9+fV6XeLiGORPDI=
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e/go.mod h1:TelZuq26kz2jysARBwOrTv16629hyUsHmIoj54QqyFo= github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e/go.mod h1:TelZuq26kz2jysARBwOrTv16629hyUsHmIoj54QqyFo=
github.com/galeone/tensorflow/tensorflow/go v0.0.0-20240119075110-6ad3cf65adfe h1:7yELf1NFEwECpXMGowkoftcInMlVtLTCdwWLmxKgzNM= github.com/galeone/tensorflow/tensorflow/go v0.0.0-20240119075110-6ad3cf65adfe h1:7yELf1NFEwECpXMGowkoftcInMlVtLTCdwWLmxKgzNM=
@ -18,6 +20,12 @@ github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99 h1:8Bt1P/zy1gb37L4n8C
github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc= github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@ -26,6 +34,8 @@ github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
@ -52,10 +62,14 @@ golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -63,6 +77,10 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=

View File

@ -309,57 +309,68 @@ func processZipFileExpand(c *Context, model *BaseModel) {
ids[name] = id ids[name] = id
} }
back_channel := make(chan int, c.Handle.Config.NumberOfWorkers)
file_chans := make([]chan *zip.File, c.Handle.Config.NumberOfWorkers)
for i := 0; i < c.Handle.Config.NumberOfWorkers; i++ {
file_chans[i] = make(chan *zip.File, 2)
go fileProcessor(c, model, reader, ids, base_path, i, file_chans[i], back_channel)
}
clean_up_channels := func() {
for i := 0; i < c.Handle.Config.NumberOfWorkers; i++ {
close(file_chans[i])
}
for i := 0; i < c.Handle.Config.NumberOfWorkers - 1; i++ {
_ = <- back_channel
}
close(back_channel)
}
first_round := true
channel_to_send := 0
// Parelalize this
for _, file := range reader.Reader.File { for _, file := range reader.Reader.File {
// Skip if dir
if file.Name[len(file.Name)-1] == '/' { if file.Name[len(file.Name)-1] == '/' {
continue continue
} }
data, err := reader.Open(file.Name) file_chans[channel_to_send] <- file
if err != nil {
failed(fmt.Sprintf("Could not open file in zip %s\n", file.Name))
return if first_round {
channel_to_send += 1
if c.Handle.Config.NumberOfWorkers == channel_to_send {
first_round = false
} }
defer data.Close() }
file_data, err := io.ReadAll(data)
if err != nil { // Can not do else if because need to handle the case where the value changes in
failed(fmt.Sprintf("Could not read file file in zip %s\n", file.Name)) // previous if
if !first_round {
new_id, ok := <- back_channel
if !ok {
c.Logger.Fatal("Something is very wrong please check as this line should be unreachable")
}
if new_id < 0 {
c.Logger.Error("Worker failed", "worker id", -(new_id + 1))
clean_up_channels()
failed("One of the workers failed due to db error")
return return
} }
// TODO check if the file is a valid photo that matched the defined photo on the database channel_to_send = new_id
parts := strings.Split(file.Name, "/")
mode := model_classes.DATA_POINT_MODE_TRAINING
if parts[0] == "testing" {
mode = model_classes.DATA_POINT_MODE_TESTING
} }
data_point_id, err := model_classes.AddDataPoint(c.Db, ids[parts[1]], "id://", mode)
if err != nil {
failed(fmt.Sprintf("Failed to add data point for %s\n", model.Id))
return
} }
file_path := path.Join(base_path, data_point_id+"."+model.Format) clean_up_channels()
f, err := os.Create(file_path)
if err != nil {
failed(fmt.Sprintf("Could not create file %s\n", file_path))
return
}
defer f.Close()
f.Write(file_data)
if !testImgForModel(c, model, file_path) {
c.Logger.Errorf("Image did not have valid format for model %s (in zip: %s)!", file_path, file.Name)
c.Logger.Warn("Not failling updating data point to status -1")
message := "Image did not have valid format for the model"
if err = model_classes.UpdateDataPointStatus(c.Db, data_point_id, -1, &message); err != nil {
failed(fmt.Sprintf("Failed to update data point status"))
return
}
}
}
c.Logger.Info("Added data to model", "id", model.Id) c.Logger.Info("Added data to model", "id", model.Id)
ModelUpdateStatus(c, model.Id, READY) ModelUpdateStatus(c, model.Id, READY)

View File

@ -5,6 +5,31 @@ import (
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
) )
// Auth level set when path is definied as 1
func handleStats(c *Context) *Error {
var b struct {
Id string `json:"id" validate:"required"`
}
if _err := c.ToJSON(&b); _err != nil {
return _err;
}
type Row struct {
Name string `db:"mc.name" json:"name"`
Training string `db:"count(mdp.id) filter (where mdp.model_mode=1)" json:"training"`
Testing string `db:"count(mdp.id) filter (where mdp.model_mode=2)" json:"testing"`
}
rows, err := GetDbMultitple[Row](c, "model_data_point as mdp inner join model_classes as mc on mc.id=mdp.class_id where mc.model_id=$1 group by mc.name order by mc.name asc;", b.Id)
if err != nil {
return c.Error500(err)
}
c.ShowMessage = false
return c.SendJSON(rows)
}
func handleList(handle *Handle) { func handleList(handle *Handle) {
handle.Get("/models", func(c *Context) *Error { handle.Get("/models", func(c *Context) *Error {
if !c.CheckAuthLevel(1) { if !c.CheckAuthLevel(1) {
@ -21,6 +46,9 @@ func handleList(handle *Handle) {
return c.Error500(nil) return c.Error500(nil)
} }
c.ShowMessage = true
return c.SendJSON(got) return c.SendJSON(got)
}) })
handle.PostAuth("/models/class/stats", 1, handleStats)
} }

View File

@ -15,6 +15,7 @@ import (
dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/go-playground/validator/v10"
"github.com/goccy/go-json" "github.com/goccy/go-json"
) )
@ -43,6 +44,7 @@ type Handle struct {
posts []HandleFunc posts []HandleFunc
deletes []HandleFunc deletes []HandleFunc
Config Config Config Config
validate *validator.Validate
} }
func decodeBody(r *http.Request) (string, *Error) { func decodeBody(r *http.Request) (string, *Error) {
@ -79,6 +81,17 @@ func (x *Handle) Post(path string, fn func(c *Context) *Error) {
x.posts = append(x.posts, HandleFunc{path, fn}) x.posts = append(x.posts, HandleFunc{path, fn})
} }
func (x *Handle) PostAuth(path string, authLevel int, fn func(c *Context) *Error) {
inner_fn := func(c *Context) *Error {
if !c.CheckAuthLevel(authLevel) {
return nil
}
return fn(c)
}
x.posts = append(x.posts, HandleFunc{path, inner_fn})
}
func (x *Handle) Delete(path string, fn func(c *Context) *Error) { func (x *Handle) Delete(path string, fn func(c *Context) *Error) {
x.deletes = append(x.deletes, HandleFunc{path, fn}) x.deletes = append(x.deletes, HandleFunc{path, fn})
} }
@ -195,6 +208,12 @@ func (c Context) ToJSON(dat any) *Error {
return c.Error500(err) return c.Error500(err)
} }
err = c.Handle.validate.Struct(dat)
if err != nil {
c.Logger.Error("Failed invalid json passed", "dat", dat, "err", err)
return c.JsonBadRequest("Bad Request! Invalid body passed!")
}
return nil return nil
} }
@ -476,7 +495,8 @@ func NewHandler(db *sql.DB, config Config) *Handle {
var gets []HandleFunc var gets []HandleFunc
var posts []HandleFunc var posts []HandleFunc
var deletes []HandleFunc var deletes []HandleFunc
x := &Handle{db, gets, posts, deletes, config} validate := validator.New()
x := &Handle{db, gets, posts, deletes, config, validate}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

Binary file not shown.

View File

@ -3,7 +3,8 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev:raw": "vite dev",
"dev": "vite dev --port 5001 --host",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@ -30,5 +31,9 @@
"typescript": "^5.0.0", "typescript": "^5.0.0",
"vite": "^5.0.3" "vite": "^5.0.3"
}, },
"type": "module" "type": "module",
"dependencies": {
"chart.js": "^4.4.2",
"svelte-chartjs": "^3.1.5"
}
} }

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
let {active} = $props<{active?: string}>(); let {active, nobox} = $props<{active?: string, nobox?: boolean}>();
function setActive(name: string) { function setActive(name: string) {
return () => active = name; return () => active = name;
@ -10,7 +10,7 @@
} }
</script> </script>
<div class="tabs"> <div class="tabs" class:nobox>
<div class="tab-buttons"> <div class="tab-buttons">
<slot name="buttons" {setActive} {isActive} /> <slot name="buttons" {setActive} {isActive} />
</div> </div>
@ -58,5 +58,16 @@
display: block; display: block;
box-shadow: 0 2px 2px 1px #66666655; box-shadow: 0 2px 2px 1px #66666655;
} }
&.nobox {
box-shadow: none;
overflow: visible;
:global(.content.selected) {
box-shadow: none;
}
}
} }
</style> </style>

View File

@ -25,31 +25,33 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from 'svelte';
import BaseModelInfo from "./BaseModelInfo.svelte"; import BaseModelInfo from './BaseModelInfo.svelte';
import DeleteModel from "./DeleteModel.svelte"; import DeleteModel from './DeleteModel.svelte';
import { goto } from "$app/navigation"; import { goto } from '$app/navigation';
import { get, rdelete } from "src/lib/requests.svelte"; import { get, rdelete } from 'src/lib/requests.svelte';
import MessageSimple from "$lib/MessageSimple.svelte" import MessageSimple from '$lib/MessageSimple.svelte';
import ModelData from "./ModelData.svelte"; import ModelData from './ModelData.svelte';
import DeleteZip from "./DeleteZip.svelte"; import DeleteZip from './DeleteZip.svelte';
import 'src/styles/forms.css' import ModelDataPage from './ModelDataPage.svelte';
import RunModel from "./RunModel.svelte";
import 'src/styles/forms.css';
import RunModel from './RunModel.svelte';
import Tabs from 'src/lib/Tabs.svelte';
let model: Promise<Model> = $state(new Promise(() => {})); let model: Promise<Model> = $state(new Promise(() => {}));
let _model: Model | undefined = $state(undefined);
let definitions: Promise<Definitions[]> = $state(new Promise(() => {})); let definitions: Promise<Definitions[]> = $state(new Promise(() => {}));
let id: string | undefined = $state() let id: string | undefined = $state();
async function getModel() { async function getModel() {
try { try {
let temp_model: Model = await get(`models/edit?id=${id}`); let temp_model: Model = await get(`models/edit?id=${id}`);
console.log(temp_model) if ([3, 6].includes(temp_model.status)) {
if (temp_model.status == 3) {
setTimeout(getModel, 2000); setTimeout(getModel, 2000);
} }
@ -60,11 +62,12 @@
} }
model = Promise.resolve(temp_model); model = Promise.resolve(temp_model);
_model = temp_model;
} catch (e) { } catch (e) {
if (e instanceof Response) { if (e instanceof Response) {
model = Promise.reject(await e.json()) model = Promise.reject(await e.json());
} else { } else {
model = Promise.reject("Could not load model"); model = Promise.reject('Could not load model');
} }
} }
} }
@ -73,7 +76,7 @@
let url = new URLSearchParams(window.location.search); let url = new URLSearchParams(window.location.search);
const _id = url.get('id'); const _id = url.get('id');
if (!_id) { if (!_id) {
goto('/models') goto('/models');
return; return;
} }
@ -84,21 +87,21 @@
let resetMessages: MessageSimple; let resetMessages: MessageSimple;
async function resetModel() { async function resetModel() {
resetMessages.display(""); resetMessages.display('');
let _model = await model; let _model = await model;
try { try {
await rdelete('models/train/reset', { await rdelete('models/train/reset', {
"id": _model.id id: _model.id
}); });
getModel(); getModel();
} catch (e) { } catch (e) {
if (e instanceof Response) { if (e instanceof Response) {
resetMessages.display(await e.json()) resetMessages.display(await e.json());
} else { } else {
resetMessages.display("Could not reset model!") resetMessages.display('Could not reset model!');
} }
} }
} }
@ -127,6 +130,29 @@
--> -->
<main> <main>
<Tabs active="model" let:isActive nobox>
<div slot="buttons" let:setActive let:isActive>
<button
class="tab"
on:click|preventDefault={setActive('model')}
class:selected={isActive('model')}
>
Model
</button>
{#if _model && [2, 3, 4, 5, 6, 7].includes(_model.status)}
<button
class="tab"
on:click|preventDefault={setActive('model-data')}
class:selected={isActive('model-data')}
>
Model Data
</button>
{/if}
</div>
{#if _model}
<ModelDataPage model={_model} on:reload={getModel} active={isActive('model-data')} />
{/if}
<div class="content" class:selected={isActive('model')}>
{#await model} {#await model}
Loading Loading
{:then m} {:then m}
@ -285,9 +311,7 @@
<BaseModelInfo model={m} /> <BaseModelInfo model={m} />
<RunModel model={m} /> <RunModel model={m} />
{#if m.status == 6} {#if m.status == 6}
<div class="card"> <div class="card">Model expading... Processing ZIP file</div>
Model expading... Processing ZIP file
</div>
{/if} {/if}
{#if m.status == -6} {#if m.status == -6}
<DeleteZip model={m} on:reload={getModel} expand /> <DeleteZip model={m} on:reload={getModel} expand />
@ -295,18 +319,19 @@
{#if m.status == -7} {#if m.status == -7}
<form> <form>
<!-- TODO add more info about the failure --> <!-- TODO add more info about the failure -->
Failed to train the model! Failed to train the model! Try to retrain
Try to retrain
</form> </form>
{/if} {/if}
{#if m.model_type == 2} {#if m.model_type == 2}
<ModelData model={m} on:reload={getModel} /> <ModelData simple model={m} on:reload={getModel} />
{/if} {/if}
<DeleteModel model={m} /> <DeleteModel model={m} />
{:else} {:else}
<h1>Unknown Status of the model.</h1> <h1>Unknown Status of the model.</h1>
{/if} {/if}
{/await} {/await}
</div>
</Tabs>
</main> </main>
<style lang="scss"> <style lang="scss">

View File

@ -3,19 +3,20 @@
name: string; name: string;
id: string; id: string;
status: number; status: number;
} };
</script> </script>
<script lang="ts">
import FileUpload from "src/lib/FileUpload.svelte";
import Tabs from "src/lib/Tabs.svelte";
import type { Model } from "./+page.svelte";
import { postFormData, get } from "src/lib/requests.svelte";
import MessageSimple from "src/lib/MessageSimple.svelte";
import { createEventDispatcher } from "svelte";
import ModelTable from "./ModelTable.svelte";
import TrainModel from "./TrainModel.svelte";
let { model } = $props<{model: Model}>(); <script lang="ts">
import FileUpload from 'src/lib/FileUpload.svelte';
import Tabs from 'src/lib/Tabs.svelte';
import type { Model } from './+page.svelte';
import { postFormData, get } from 'src/lib/requests.svelte';
import MessageSimple from 'src/lib/MessageSimple.svelte';
import { createEventDispatcher } from 'svelte';
import ModelTable from './ModelTable.svelte';
import TrainModel from './TrainModel.svelte';
let { model, simple } = $props<{ model: Model; simple?: boolean }>();
let classes: Class[] = $state([]); let classes: Class[] = $state([]);
let has_data: boolean = $state(false); let has_data: boolean = $state(false);
@ -23,10 +24,10 @@
let file: File | undefined = $state(); let file: File | undefined = $state();
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
reload: void reload: void;
}>(); }>();
let uploading: Promise<void> = $state(Promise.resolve()) let uploading: Promise<void> = $state(Promise.resolve());
let numberOfInvalidImages = $state(0); let numberOfInvalidImages = $state(0);
let uploadImage: MessageSimple; let uploadImage: MessageSimple;
@ -62,47 +63,48 @@
if (!model) return; if (!model) return;
try { try {
let data = await get(`models/edit/classes?id=${model.id}`); let data = await get(`models/edit/classes?id=${model.id}`);
classes = data.classes classes = data.classes;
numberOfInvalidImages = data.number_of_invalid_images; numberOfInvalidImages = data.number_of_invalid_images;
has_data = data.has_data; has_data = data.has_data;
} catch { } catch {
return; return;
} }
} }
</script> </script>
<div class="card"> {#if !simple}
<h3> <div class="card">
Training data <h3>Training data</h3>
</h3>
{#if classes.length == 0} {#if classes.length == 0}
<p> <p>You need to upload data so the model can train.</p>
You need to upload data so the model can train.
</p>
<Tabs active="upload" let:isActive> <Tabs active="upload" let:isActive>
<div slot="buttons" let:setActive let:isActive> <div slot="buttons" let:setActive let:isActive>
<button class="tab" class:selected={isActive("upload")} on:click={setActive("upload")}> <button class="tab" class:selected={isActive('upload')} on:click={setActive('upload')}>
Upload Upload
</button> </button>
<button class="tab" class:selected={isActive("create-class")} on:click={setActive("create-class")}> <button
class="tab"
class:selected={isActive('create-class')}
on:click={setActive('create-class')}
>
Create Class Create Class
</button> </button>
<button class="tab" class:selected={isActive("api")} on:click={setActive("api")}> <button class="tab" class:selected={isActive('api')} on:click={setActive('api')}>
Api Api
</button> </button>
</div> </div>
<div class="content" class:selected={isActive("upload")}> <div class="content" class:selected={isActive('upload')}>
<form on:submit|preventDefault={uploadZip}> <form on:submit|preventDefault={uploadZip}>
<fieldset class="file-upload" > <fieldset class="file-upload">
<label for="file">Data file</label> <label for="file">Data file</label>
<div class="form-msg"> <div class="form-msg">
Please provide a file that has the training and testing data<br/> Please provide a file that has the training and testing data<br />
The file must have 2 folders one with testing images and one with training images. <br/> The file must have 2 folders one with testing images and one with training images.
Each of the folders will contain the classes of the model. The folders must be the same in testing and training. <br />
The class folders must have the images for the classes. Each of the folders will contain the classes of the model. The folders must be the same
<pre> in testing and training. The class folders must have the images for the classes.
training\ <pre>
training\
class1\ class1\
img1.png img1.png
img2.png img2.png
@ -114,7 +116,7 @@
img2.png img2.png
... ...
... ...
testing\ testing\
class1\ class1\
img1.png img1.png
img2.png img2.png
@ -128,70 +130,66 @@
... ...
</pre> </pre>
</div> </div>
<FileUpload replace_slot bind:file={file} accept="application/zip" notExpand > <FileUpload replace_slot bind:file accept="application/zip" notExpand>
<img src="/imgs/upload-icon.png" alt="" /> <img src="/imgs/upload-icon.png" alt="" />
<span> <span> Upload Zip File </span>
Upload Zip File
</span>
<div slot="replaced" style="display: inline;"> <div slot="replaced" style="display: inline;">
<img src="/imgs/upload-icon.png" alt="" /> <img src="/imgs/upload-icon.png" alt="" />
<span> <span> File selected </span>
File selected
</span>
</div> </div>
</FileUpload> </FileUpload>
</fieldset> </fieldset>
<MessageSimple bind:this={uploadImage} /> <MessageSimple bind:this={uploadImage} />
{#if file} {#if file}
{#await uploading} {#await uploading}
<button disabled> <button disabled> Uploading </button>
Uploading
</button>
{:then} {:then}
<button> <button> Add </button>
Add
</button>
{/await} {/await}
{/if} {/if}
</form> </form>
</div> </div>
<div class="content" class:selected={isActive("create-class")}> <div class="content" class:selected={isActive('create-class')}>
<ModelTable {classes} {model} on:reload={() => dispatch('reload')} /> <ModelTable {classes} {model} on:reload={() => dispatch('reload')} />
</div> </div>
<div class="content" class:selected={isActive("api")}> <div class="content" class:selected={isActive('api')}>TODO</div>
TODO
</div>
</Tabs> </Tabs>
<div class="tabs"> <div class="tabs"></div>
</div>
{:else} {:else}
<p> <p>You need to upload data so the model can train.</p>
You need to upload data so the model can train.
</p>
{#if numberOfInvalidImages > 0} {#if numberOfInvalidImages > 0}
<p class="danger"> <p class="danger">
There are images {numberOfInvalidImages} that were loaded that do not have the correct format. These images will be delete when the model trains. There are images {numberOfInvalidImages} that were loaded that do not have the correct format.
These images will be delete when the model trains.
</p> </p>
{/if} {/if}
<Tabs active="create-class" let:isActive> <Tabs active="create-class" let:isActive>
<div slot="buttons" let:setActive let:isActive> <div slot="buttons" let:setActive let:isActive>
<button class="tab" class:selected={isActive("create-class")} on:click={setActive("create-class")}> <button
class="tab"
class:selected={isActive('create-class')}
on:click={setActive('create-class')}
>
Create Class Create Class
</button> </button>
<button class="tab" class:selected={isActive("api")} on:click={setActive("api")}> <button class="tab" class:selected={isActive('api')} on:click={setActive('api')}>
Api Api
</button> </button>
</div> </div>
<div class="content" class:selected={isActive("create-class")}> <div class="content" class:selected={isActive('create-class')}>
<ModelTable {classes} {model} on:reload={() => dispatch('reload')} /> <ModelTable {classes} {model} on:reload={() => dispatch('reload')} />
</div> </div>
<div class="content" class:selected={isActive("api")}> <div class="content" class:selected={isActive('api')}>TODO</div>
TODO
</div>
</Tabs> </Tabs>
{/if} {/if}
</div> </div>
{/if}
{#if classes.some((item) => item.status == 1) && ![-6, 6].includes(model.status)} {#if classes.some((item) => item.status == 1) && ![-6, 6].includes(model.status)}
<TrainModel number_of_invalid_images={numberOfInvalidImages} {model} {has_data} on:reload={() => dispatch('reload')} /> <TrainModel
number_of_invalid_images={numberOfInvalidImages}
{model}
{has_data}
on:reload={() => dispatch('reload')}
/>
{/if} {/if}

View File

@ -0,0 +1,48 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { Model } from "./+page.svelte";
import ModelData from "./ModelData.svelte";
import { post } from "src/lib/requests.svelte";
import ModelDataPageStatsGraph from "./ModelDataPageStatsGraph.svelte";
import type { ModelStats } from "./types";
import DeleteZip from "./DeleteZip.svelte";
let { model, active } = $props<{model: Model, active?: boolean}>()
const dispatch = createEventDispatcher<{ reload: void }>();
$effect(() => {
if (active)
getData();
});
let stats: ModelStats | undefined = $state();
async function getData() {
if (!model) return;
try {
stats = await post(`models/class/stats`, { id: model.id });
} catch {
return;
}
}
</script>
<div class="content" class:selected={active}>
{#if stats}
<ModelDataPageStatsGraph data={stats} />
{/if}
{#if [-6, -2].includes(model.status)}
<DeleteZip model={model} on:reload={() => dispatch('reload')} expand />
{/if}
<ModelData model={model} on:reload={() => {
getData();
dispatch('reload');
}} />
</div>

View File

@ -0,0 +1,89 @@
<script lang="ts">
import {
Chart,
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale,
BarController,
Colors
} from 'chart.js';
import type { ModelStats } from './types';
Chart.register(
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale,
BarController,
Colors
);
let { data } = $props<{ data: ModelStats }>();
let ctx: HTMLCanvasElement;
let chart: Chart;
$effect(() => {
if (data && ctx) {
if (chart) {
console.log("update")
chart.data = {
labels: data.map((a) => a.name),
datasets: [
{
label: 'Training',
data: data.map((a) => a.training)
},
{
label: 'Testing',
data: data.map((a) => a.testing)
}
]
};
chart.update('resize');
} else {
console.log("create")
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.map((a) => a.name),
datasets: [
{
label: 'Training',
data: data.map((a) => a.training)
},
{
label: 'Testing',
data: data.map((a) => a.testing)
}
]
},
options: {
scales: {
x: {
stacked: true
},
y: {
stacked: true
}
}
}
});
}
}
});
</script>
<div><canvas bind:this={ctx} /></div>
<style lang="scss">
canvas {
width: 100%;
}
</style>

View File

@ -44,8 +44,6 @@
let res = await get('models/data/list?' + url.toString()); let res = await get('models/data/list?' + url.toString());
showNext = res.showNext; showNext = res.showNext;
image_list = res.image_list; image_list = res.image_list;
console.log(image_list);
} catch (e) { } catch (e) {
console.error('TODO notify user', e); console.error('TODO notify user', e);
} }

View File

@ -0,0 +1,5 @@
export type ModelStats = Array<{
name: string,
training: number,
testing: number,
}>