Compare commits

..

No commits in common. "3d12e00bfcb35253313e3fd30d797af1fd0f95e6" and "988abb715feb5d67dca1d7d82ca834babc622921" have entirely different histories.

12 changed files with 47 additions and 58 deletions

View File

@ -65,10 +65,3 @@ Usage of record:
-record-json-path string -record-json-path string
Path where to write json files, use RECORD_JSON_PATH if args not set Path where to write json files, use RECORD_JSON_PATH if args not set
``` ```
## Useful
Debug record:
go run ./cmd/rc-tools display record -mqtt-broker tcp://diabolo.local:1883 -mqtt-username satanas -mqtt-password satanas -mqtt-client-id display-record -mqtt-topic-records car/satanas/part/records

View File

@ -55,11 +55,12 @@ func main() {
cli.SetDefaultValueFromEnv(&ociImage, "TRAIN_OCI_IMAGE", "") cli.SetDefaultValueFromEnv(&ociImage, "TRAIN_OCI_IMAGE", "")
cli.SetDefaultValueFromEnv(&bucket, "TRAIN_BUCKET", "") cli.SetDefaultValueFromEnv(&bucket, "TRAIN_BUCKET", "")
flag.BoolVar(&debug, "debug", false, "Display debug logs") flag.BoolVar(&debug, "debug", false, "Display debug logs")
displayFlags := flag.NewFlagSet("display", flag.ExitOnError) displayFlags := flag.NewFlagSet("display", flag.ExitOnError)
displayFlags.Usage = func() { displayFlags.Usage = func(){
fmt.Printf("Usage of %s %s:\n", os.Args[0], displayFlags.Name()) fmt.Printf("Usage of %s %s:\n", os.Args[0], displayFlags.Name())
fmt.Printf(" camera\n \tLive from car camera\n") fmt.Printf(" camera\n \tLive from car camera\n")
fmt.Printf(" record\n \tLive from published records\n") fmt.Printf(" record\n \tLive from published records\n")
@ -86,13 +87,15 @@ func main() {
recordFlags.StringVar(&recordTopic, "mqtt-topic-records", os.Getenv("MQTT_TOPIC_RECORDS"), "Mqtt topic that contains record data for training, use MQTT_TOPIC_RECORDS if args not set") recordFlags.StringVar(&recordTopic, "mqtt-topic-records", os.Getenv("MQTT_TOPIC_RECORDS"), "Mqtt topic that contains record data for training, use MQTT_TOPIC_RECORDS if args not set")
recordFlags.StringVar(&recordsPath, "record-path", os.Getenv("RECORD_PATH"), "Path where to write records files, use RECORD_PATH if args not set") recordFlags.StringVar(&recordsPath, "record-path", os.Getenv("RECORD_PATH"), "Path where to write records files, use RECORD_PATH if args not set")
var basedir, destdir string var basedir, destdir string
impdkFlags := flag.NewFlagSet("import-donkey-records", flag.ExitOnError) impdkFlags := flag.NewFlagSet("import-donkey-records", flag.ExitOnError)
impdkFlags.StringVar(&basedir, "from", "", "source directory") impdkFlags.StringVar(&basedir, "from", "", "source directory")
impdkFlags.StringVar(&destdir, "to", "", "destination directory") impdkFlags.StringVar(&destdir, "to", "", "destination directory")
trainingFlags := flag.NewFlagSet("training", flag.ExitOnError) trainingFlags := flag.NewFlagSet("training", flag.ExitOnError)
trainingFlags.Usage = func() { trainingFlags.Usage = func(){
fmt.Printf("Usage of %s %s:\n", os.Args[0], trainingFlags.Name()) fmt.Printf("Usage of %s %s:\n", os.Args[0], trainingFlags.Name())
fmt.Printf(" list\n \tList existing training jobs\n") fmt.Printf(" list\n \tList existing training jobs\n")
fmt.Printf(" archive\n \tBuild tar.gz archive for training\n") fmt.Printf(" archive\n \tBuild tar.gz archive for training\n")
@ -130,6 +133,7 @@ func main() {
trainArchiveFlags.IntVar(&horizon, "horizon", 0, "Upper zone image to crop (in pixels)") trainArchiveFlags.IntVar(&horizon, "horizon", 0, "Upper zone image to crop (in pixels)")
trainArchiveFlags.BoolVar(&withFlipImage, "with-flip-image", withFlipImage, "Flip horiontal image and reverse steering to increase data into training archive") trainArchiveFlags.BoolVar(&withFlipImage, "with-flip-image", withFlipImage, "Flip horiontal image and reverse steering to increase data into training archive")
modelsFlags := flag.NewFlagSet("models", flag.ExitOnError) modelsFlags := flag.NewFlagSet("models", flag.ExitOnError)
modelsFlags.Usage = func() { modelsFlags.Usage = func() {
fmt.Printf("Usage of %s %s:\n", os.Args[0], modelsFlags.Name()) fmt.Printf("Usage of %s %s:\n", os.Args[0], modelsFlags.Name())
@ -224,7 +228,7 @@ func main() {
} }
switch trainingFlags.Arg(0) { switch trainingFlags.Arg(0) {
case trainingListJobFlags.Name(): case trainingListJobFlags.Name():
if err := trainingListJobFlags.Parse(os.Args[3:]); err == flag.ErrHelp { if err:= trainingListJobFlags.Parse(os.Args[3:]); err == flag.ErrHelp {
trainingListJobFlags.PrintDefaults() trainingListJobFlags.PrintDefaults()
os.Exit(0) os.Exit(0)
} }
@ -245,6 +249,7 @@ func main() {
trainingFlags.PrintDefaults() trainingFlags.PrintDefaults()
os.Exit(0) os.Exit(0)
} }
case modelsFlags.Name(): case modelsFlags.Name():
@ -254,13 +259,13 @@ func main() {
} }
switch modelsFlags.Arg(0) { switch modelsFlags.Arg(0) {
case modelsListFlags.Name(): case modelsListFlags.Name():
if err := modelsListFlags.Parse(os.Args[3:]); err == flag.ErrHelp { if err:= modelsListFlags.Parse(os.Args[3:]); err == flag.ErrHelp {
modelsListFlags.PrintDefaults() modelsListFlags.PrintDefaults()
os.Exit(0) os.Exit(0)
} }
runModelsList(bucket) runModelsList(bucket)
case modelsDownloadFlags.Name(): case modelsDownloadFlags.Name():
if err := modelsDownloadFlags.Parse(os.Args[3:]); err == flag.ErrHelp { if err:= modelsDownloadFlags.Parse(os.Args[3:]); err == flag.ErrHelp {
modelsDownloadFlags.PrintDefaults() modelsDownloadFlags.PrintDefaults()
os.Exit(0) os.Exit(0)
} }
@ -315,7 +320,7 @@ func runImportDonkeyRecords(basedir, destdir string) {
zap.S().Fatalf("unable to import files from %v to %v: %v", basedir, destdir, err) zap.S().Fatalf("unable to import files from %v to %v: %v", basedir, destdir, err)
} }
} }
func runDisplayRecord(client mqtt.Client, recordTopic string) { func runDisplayRecord(client mqtt.Client, recordTopic string){
r := display.NewRecordDisplay(client, recordTopic) r := display.NewRecordDisplay(client, recordTopic)
defer r.Stop() defer r.Stop()
@ -402,4 +407,4 @@ func runModelsDownload(bucketName, modelPath, output string) {
if err != nil { if err != nil {
zap.S().Fatalf("unable to download model: %s", err) zap.S().Fatalf("unable to download model: %s", err)
} }
} }

2
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.26.11 github.com/aws/aws-sdk-go-v2/service/s3 v1.26.11
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.32.1 github.com/aws/aws-sdk-go-v2/service/sagemaker v1.32.1
github.com/cyrilix/robocar-base v0.1.7 github.com/cyrilix/robocar-base v0.1.7
github.com/cyrilix/robocar-protobuf/go v1.1.0 github.com/cyrilix/robocar-protobuf/go v1.0.5
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/eclipse/paho.mqtt.golang v1.4.1 github.com/eclipse/paho.mqtt.golang v1.4.1
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.2

4
go.sum
View File

@ -38,8 +38,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/cyrilix/robocar-base v0.1.7 h1:EVzZ0KjigSFpke5f3A/PybEH3WFUEIrYSc3z/dhOZ48= github.com/cyrilix/robocar-base v0.1.7 h1:EVzZ0KjigSFpke5f3A/PybEH3WFUEIrYSc3z/dhOZ48=
github.com/cyrilix/robocar-base v0.1.7/go.mod h1:4E11HQSNy2NT8e7MW188y6ST9C0RzarKyn7sK/3V/Lk= github.com/cyrilix/robocar-base v0.1.7/go.mod h1:4E11HQSNy2NT8e7MW188y6ST9C0RzarKyn7sK/3V/Lk=
github.com/cyrilix/robocar-protobuf/go v1.1.0 h1:txIjGnnCF3UzedpsWu+sL7nMA+pNjSnX6HZlAmuReH4= github.com/cyrilix/robocar-protobuf/go v1.0.5 h1:PX1At+pf6G7gJwT4LzJLQu3/LPFTTNNlZmZSYtnSELY=
github.com/cyrilix/robocar-protobuf/go v1.1.0/go.mod h1:Y3AE28K5V7EZxMXp/6A8RhkRz15VOfFy4CjST35FbtQ= github.com/cyrilix/robocar-protobuf/go v1.0.5/go.mod h1:Y3AE28K5V7EZxMXp/6A8RhkRz15VOfFy4CjST35FbtQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@ -11,7 +11,6 @@ import (
"image" "image"
"image/color" "image/color"
"log" "log"
"time"
) )
func NewPart(client mqtt.Client, frameTopic, objectsTopic, roadTopic string, withObjects, withRoad bool) *FramePart { func NewPart(client mqtt.Client, frameTopic, objectsTopic, roadTopic string, withObjects, withRoad bool) *FramePart {
@ -53,11 +52,9 @@ func (p *FramePart) Start() error {
var img = gocv.NewMat() var img = gocv.NewMat()
var objectsMsg events.ObjectsMessage var objectsMsg events.ObjectsMessage
var roadMsg events.RoadMessage var roadMsg events.RoadMessage
ticker := time.NewTicker(1 * time.Second)
for { for {
select { select {
case <-ticker.C:
img = gocv.NewMatWithSize(120, 120, gocv.MatTypeCV8S)
case newImg := <-p.imgChan: case newImg := <-p.imgChan:
img.Close() img.Close()
img = newImg img = newImg
@ -70,7 +67,6 @@ func (p *FramePart) Start() error {
return nil return nil
} }
p.drawFrame(&img, &objectsMsg, &roadMsg) p.drawFrame(&img, &objectsMsg, &roadMsg)
ticker.Reset(1 * time.Second)
} }
} }
@ -109,7 +105,6 @@ func (p *FramePart) onObjects(_ mqtt.Client, message mqtt.Message) {
return return
} }
zap.S().Infow("new objects", zap.String("topic", message.Topic()), zap.String("object", msg.Objects[0].String()))
p.objectsChan <- msg p.objectsChan <- msg
} }
@ -161,17 +156,11 @@ func (p *FramePart) drawFrame(img *gocv.Mat, objects *events.ObjectsMessage, roa
} }
func (p *FramePart) drawObjects(img *gocv.Mat, objects *events.ObjectsMessage) { func (p *FramePart) drawObjects(img *gocv.Mat, objects *events.ObjectsMessage) {
zap.S().Debugf("draw object %v", objects)
for _, obj := range objects.GetObjects() { for _, obj := range objects.GetObjects() {
gocv.Rectangle( gocv.Rectangle(
img, img,
image.Rect( image.Rect(int(obj.GetLeft()), int(obj.GetTop()), int(obj.GetRight()), int(obj.GetBottom())),
int(obj.GetLeft()*float32(img.Cols())), color.RGBA{0, 255, 0, 0},
int(obj.GetTop()*float32(img.Rows())),
int(obj.GetRight()*float32(img.Cols())),
int(obj.GetBottom()*float32(img.Rows())),
),
color.RGBA{R: 0, G: 255, B: 0, A: 0},
2) 2)
} }
} }

View File

@ -203,7 +203,7 @@ func addJsonFiles(recordFiles []string, imgCam []string, flipImage bool, w *zip.
if flipImage { if flipImage {
rcd.UserAngle = rcd.UserAngle * -1 rcd.UserAngle = rcd.UserAngle * -1
rcd.CamImageArray = fmt.Sprintf("flip_%s", camName) rcd.CamImageArray = fmt.Sprintf("flip_%s", camName)
} else { }else {
rcd.CamImageArray = camName rcd.CamImageArray = camName
} }

View File

@ -96,6 +96,7 @@ func (r *Record) drawRecord(rec *events.RecordMessage, objects *events.ObjectsMe
r.window.WaitKey(1) r.window.WaitKey(1)
} }
func (r *Record) drawSteering(img *gocv.Mat, steering float32) { func (r *Record) drawSteering(img *gocv.Mat, steering float32) {
gocv.PutText( gocv.PutText(
img, img,

View File

@ -20,10 +20,10 @@ func ListModels(ctx context.Context, bucket string) error {
outputs, err := client.ListObjectsV2( outputs, err := client.ListObjectsV2(
ctx, ctx,
&s3.ListObjectsV2Input{ &s3.ListObjectsV2Input{
Bucket: aws.String(bucket), Bucket: aws.String(bucket),
Prefix: aws.String("output"), Prefix: aws.String("output"),
}, },
) )
if err != nil { if err != nil {
return fmt.Errorf("unable to list models in bucket %v: %w", bucket, err) return fmt.Errorf("unable to list models in bucket %v: %w", bucket, err)
} }
@ -38,7 +38,7 @@ func DownloadArchive(ctx context.Context, bucketName, modelPath string) ([]byte,
l := zap.S().With( l := zap.S().With(
"bucket", bucketName, "bucket", bucketName,
"model", modelPath, "model", modelPath,
) )
client := s3.NewFromConfig(awsutils.MustLoadConfig()) client := s3.NewFromConfig(awsutils.MustLoadConfig())
l.Debug("download model") l.Debug("download model")
@ -46,8 +46,8 @@ func DownloadArchive(ctx context.Context, bucketName, modelPath string) ([]byte,
ctx, ctx,
&s3.GetObjectInput{ &s3.GetObjectInput{
Bucket: aws.String(bucketName), Bucket: aws.String(bucketName),
Key: aws.String(modelPath), Key: aws.String(modelPath),
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to download model: %w", err) return nil, fmt.Errorf("unable to download model: %w", err)
@ -70,4 +70,4 @@ func DownloadArchiveToFile(ctx context.Context, bucketName, modelPath, outputFil
return fmt.Errorf("unable to write model '%s' to file '%s': %v", modelPath, outputFile, err) return fmt.Errorf("unable to write model '%s' to file '%s': %v", modelPath, outputFile, err)
} }
return nil return nil
} }

View File

@ -1,5 +1,6 @@
package train package train
const ( const (
prefixInput = "input/data/train/" prefixInput = "input/data/train/"
) )

View File

@ -6,8 +6,8 @@ set +x
RECORDS_PATH=~/robocar/record-sim4-2 RECORDS_PATH=~/robocar/record-sim4-2
#TRAINING_OPTS="--horizon=20" #TRAINING_OPTS="--horizon=20"
TRAINING_OPTS="" TRAINING_OPTS=""
MODEL_TYPE="categorical" #MODEL_TYPE="categorical"
#MODEL_TYPE="linear" MODEL_TYPE="linear"
IMG_WIDTH=160 IMG_WIDTH=160
IMG_HEIGHT=120 IMG_HEIGHT=120
HORIZON=20 HORIZON=20

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.28.1 // protoc-gen-go v1.27.1
// protoc v3.21.4 // protoc v3.12.4
// source: events/events.proto // source: events/events.proto
package events package events
@ -468,17 +468,17 @@ func (x *ObjectsMessage) GetFrameRef() *FrameRef {
return nil return nil
} }
// BoundingBox that contains an object, coordinates as percent // BoundingBox that contains an object
type Object struct { type Object struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Type TypeObject `protobuf:"varint,1,opt,name=type,proto3,enum=robocar.events.TypeObject" json:"type,omitempty"` Type TypeObject `protobuf:"varint,1,opt,name=type,proto3,enum=robocar.events.TypeObject" json:"type,omitempty"`
Left float32 `protobuf:"fixed32,2,opt,name=left,proto3" json:"left,omitempty"` Left int32 `protobuf:"varint,2,opt,name=left,proto3" json:"left,omitempty"`
Top float32 `protobuf:"fixed32,3,opt,name=top,proto3" json:"top,omitempty"` Top int32 `protobuf:"varint,3,opt,name=top,proto3" json:"top,omitempty"`
Right float32 `protobuf:"fixed32,4,opt,name=right,proto3" json:"right,omitempty"` Right int32 `protobuf:"varint,4,opt,name=right,proto3" json:"right,omitempty"`
Bottom float32 `protobuf:"fixed32,5,opt,name=bottom,proto3" json:"bottom,omitempty"` Bottom int32 `protobuf:"varint,5,opt,name=bottom,proto3" json:"bottom,omitempty"`
Confidence float32 `protobuf:"fixed32,6,opt,name=confidence,proto3" json:"confidence,omitempty"` Confidence float32 `protobuf:"fixed32,6,opt,name=confidence,proto3" json:"confidence,omitempty"`
} }
@ -521,28 +521,28 @@ func (x *Object) GetType() TypeObject {
return TypeObject_ANY return TypeObject_ANY
} }
func (x *Object) GetLeft() float32 { func (x *Object) GetLeft() int32 {
if x != nil { if x != nil {
return x.Left return x.Left
} }
return 0 return 0
} }
func (x *Object) GetTop() float32 { func (x *Object) GetTop() int32 {
if x != nil { if x != nil {
return x.Top return x.Top
} }
return 0 return 0
} }
func (x *Object) GetRight() float32 { func (x *Object) GetRight() int32 {
if x != nil { if x != nil {
return x.Right return x.Right
} }
return 0 return 0
} }
func (x *Object) GetBottom() float32 { func (x *Object) GetBottom() int32 {
if x != nil { if x != nil {
return x.Bottom return x.Bottom
} }
@ -918,10 +918,10 @@ var file_events_events_proto_rawDesc = []byte{
0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x63, 0x61, 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x63, 0x61, 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x04, 0x74, 0x74, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x04, 0x74,
0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x02, 0x52, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x6f, 0x70, 0x18, 0x03, 0x05, 0x52, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x6f, 0x70, 0x18, 0x03,
0x20, 0x01, 0x28, 0x02, 0x52, 0x03, 0x74, 0x6f, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x69, 0x67, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x6f, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x69, 0x67,
0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x72, 0x69, 0x67, 0x68, 0x74, 0x12, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x72, 0x69, 0x67, 0x68, 0x74, 0x12,
0x16, 0x0a, 0x06, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x02, 0x52, 0x16, 0x0a, 0x06, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52,
0x06, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x06, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x2f, 0x0a, 0x13, 0x53, 0x77, 0x69, 0x74, 0x63, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x2f, 0x0a, 0x13, 0x53, 0x77, 0x69, 0x74, 0x63,

2
vendor/modules.txt vendored
View File

@ -111,7 +111,7 @@ github.com/aws/smithy-go/waiter
## explicit; go 1.18 ## explicit; go 1.18
github.com/cyrilix/robocar-base/cli github.com/cyrilix/robocar-base/cli
github.com/cyrilix/robocar-base/service github.com/cyrilix/robocar-base/service
# github.com/cyrilix/robocar-protobuf/go v1.1.0 # github.com/cyrilix/robocar-protobuf/go v1.0.5
## explicit; go 1.18 ## explicit; go 1.18
github.com/cyrilix/robocar-protobuf/go/events github.com/cyrilix/robocar-protobuf/go/events
# github.com/disintegration/imaging v1.6.2 # github.com/disintegration/imaging v1.6.2