diff --git a/data/capture_file.go b/data/capture_file.go index df8e9b5309b..fa3c9643e02 100644 --- a/data/capture_file.go +++ b/data/capture_file.go @@ -16,7 +16,6 @@ import ( "google.golang.org/protobuf/types/known/anypb" "go.viam.com/rdk/resource" - "go.viam.com/rdk/utils" ) // TODO Data-343: Reorganize this into a more standard interface/package, and add tests. @@ -240,73 +239,6 @@ func getFileTimestampName() string { return time.Now().Format(time.RFC3339Nano) } -// CaptureType represents captured tabular or binary data. -type CaptureType int - -const ( - // CaptureTypeUnspecified represents that the data type of the captured data was not specified. - CaptureTypeUnspecified CaptureType = iota - // CaptureTypeTabular represents that the data type of the captured data is tabular. - CaptureTypeTabular - // CaptureTypeBinary represents that the data type of the captured data is binary. - CaptureTypeBinary -) - -// ToProto converts a DataType into a v1.DataType. -func (dt CaptureType) ToProto() v1.DataType { - switch dt { - case CaptureTypeTabular: - return v1.DataType_DATA_TYPE_TABULAR_SENSOR - case CaptureTypeBinary: - return v1.DataType_DATA_TYPE_BINARY_SENSOR - case CaptureTypeUnspecified: - return v1.DataType_DATA_TYPE_UNSPECIFIED - default: - return v1.DataType_DATA_TYPE_UNSPECIFIED - } -} - -// GetDataType returns the DataType of the method. -func GetDataType(methodName string) CaptureType { - switch methodName { - case nextPointCloud, readImage, pointCloudMap, GetImages: - return CaptureTypeBinary - default: - return CaptureTypeTabular - } -} - -// getFileExt gets the file extension for a capture file. -func getFileExt(dataType CaptureType, methodName string, parameters map[string]string) string { - defaultFileExt := "" - switch dataType { - case CaptureTypeTabular: - return ".dat" - case CaptureTypeBinary: - if methodName == nextPointCloud { - return ".pcd" - } - if methodName == readImage { - // TODO: Add explicit file extensions for all mime types. - switch parameters["mime_type"] { - case utils.MimeTypeJPEG: - return ".jpeg" - case utils.MimeTypePNG: - return ".png" - case utils.MimeTypePCD: - return ".pcd" - default: - return defaultFileExt - } - } - case CaptureTypeUnspecified: - return defaultFileExt - default: - return defaultFileExt - } - return defaultFileExt -} - // SensorDataFromCaptureFilePath returns all readings in the file at filePath. // NOTE: (Nick S) At time of writing this is only used in tests. func SensorDataFromCaptureFilePath(filePath string) ([]*v1.SensorData, error) { diff --git a/data/collector.go b/data/collector.go index 57c95a4ea67..6de63f93f7b 100644 --- a/data/collector.go +++ b/data/collector.go @@ -13,328 +13,20 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.opencensus.io/trace" - dataPB "go.viam.com/api/app/data/v1" - datasyncPB "go.viam.com/api/app/datasync/v1" - camerapb "go.viam.com/api/component/camera/v1" "go.viam.com/utils" "go.viam.com/utils/protoutils" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/timestamppb" "go.viam.com/rdk/logging" - rprotoutils "go.viam.com/rdk/protoutils" "go.viam.com/rdk/resource" ) // The cutoff at which if interval < cutoff, a sleep based capture func is used instead of a ticker. var sleepCaptureCutoff = 2 * time.Millisecond -// CaptureFunc allows the creation of simple Capturers with anonymous functions. -type CaptureFunc func(ctx context.Context, params map[string]*anypb.Any) (CaptureResult, error) - -// Timestamps are the timestamps the data was captured. -type Timestamps struct { - // TimeRequested represents the time the request for the data was started - TimeRequested time.Time - // TimeRequested represents the time the response for the request for the data - // was received - TimeReceived time.Time -} - -// MimeType represents the mime type of the sensor data. -type MimeType int - -// This follows the mime types supported in -// https://github.com/viamrobotics/api/pull/571/files#diff-b77927298d8d5d5228beeea47bd0860d9b322b4f3ef45e129bc238ec17704826R75 -const ( - // MimeTypeUnspecified means that the mime type was not specified. - MimeTypeUnspecified MimeType = iota - // MimeTypeImageJpeg means that the mime type is jpeg. - MimeTypeImageJpeg - // MimeTypeImagePng means that the mime type is png. - MimeTypeImagePng - // MimeTypeApplicationPcd means that the mime type is pcd. - MimeTypeApplicationPcd -) - -// CaptureResult is the result of a capture function. -type CaptureResult struct { - Timestamps - // Type represents the type of result (binary or tabular) - Type CaptureType - // TabularData contains the tabular data payload when Type == CaptureResultTypeTabular - TabularData TabularData - // Binaries contains binary data responses when Type == CaptureResultTypeBinary - Binaries []Binary -} - -// Binary represents an element of a binary capture result response. -type Binary struct { - // Payload contains the binary payload - Payload []byte - // MimeType descibes the payload's MimeType - MimeType MimeType - // Annotations provide metadata about the Payload - Annotations Annotations -} - -// ToProto converts MimeType to datasyncPB. -func (mt MimeType) ToProto() datasyncPB.MimeType { - switch mt { - case MimeTypeUnspecified: - return datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED - case MimeTypeImageJpeg: - return datasyncPB.MimeType_MIME_TYPE_IMAGE_JPEG - case MimeTypeImagePng: - return datasyncPB.MimeType_MIME_TYPE_IMAGE_PNG - case MimeTypeApplicationPcd: - return datasyncPB.MimeType_MIME_TYPE_APPLICATION_PCD - default: - return datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED - } -} - -// TabularData contains a tabular data payload. -type TabularData struct { - Payload *structpb.Struct -} - -// BoundingBox represents a labeled bounding box -// with an optional confidence interval between 0 and 1. -type BoundingBox struct { - Label string - Confidence *float64 - XMinNormalized float64 - YMinNormalized float64 - XMaxNormalized float64 - YMaxNormalized float64 -} - -// Classification represents a labeled classification -// with an optional confidence interval between 0 and 1. -type Classification struct { - Label string - Confidence *float64 -} - -// Annotations represents ML classifications. -type Annotations struct { - BoundingBoxes []BoundingBox - Classifications []Classification -} - -// Empty returns true when Annotations are empty. -func (mt Annotations) Empty() bool { - return len(mt.BoundingBoxes) == 0 && len(mt.Classifications) == 0 -} - -// ToProto converts Annotations to *dataPB.Annotations. -func (mt Annotations) ToProto() *dataPB.Annotations { - if mt.Empty() { - return nil - } - - var bboxes []*dataPB.BoundingBox - for _, bb := range mt.BoundingBoxes { - bboxes = append(bboxes, &dataPB.BoundingBox{ - Label: bb.Label, - Confidence: bb.Confidence, - XMinNormalized: bb.XMinNormalized, - XMaxNormalized: bb.XMaxNormalized, - YMinNormalized: bb.YMinNormalized, - YMaxNormalized: bb.YMaxNormalized, - }) - } - - var classifications []*dataPB.Classification - for _, c := range mt.Classifications { - classifications = append(classifications, &dataPB.Classification{ - Label: c.Label, - Confidence: c.Confidence, - }) - } - - return &dataPB.Annotations{ - Bboxes: bboxes, - Classifications: classifications, - } -} - -// Validate returns an error if the *CaptureResult is invalid. -func (cr *CaptureResult) Validate() error { - var ts Timestamps - if cr.Timestamps.TimeRequested == ts.TimeRequested { - return errors.New("Timestamps.TimeRequested must be set") - } - - if cr.Timestamps.TimeReceived == ts.TimeReceived { - return errors.New("Timestamps.TimeRequested must be set") - } - - switch cr.Type { - case CaptureTypeTabular: - if len(cr.Binaries) > 0 { - return errors.New("tabular result can't contain binary data") - } - if cr.TabularData.Payload == nil { - return errors.New("tabular result must have non empty tabular data") - } - return nil - case CaptureTypeBinary: - if cr.TabularData.Payload != nil { - return errors.New("binary result can't contain tabular data") - } - if len(cr.Binaries) == 0 { - return errors.New("binary result must have non empty binary data") - } - - for _, b := range cr.Binaries { - if len(b.Payload) == 0 { - return errors.New("binary results can't have empty binary payload") - } - } - return nil - case CaptureTypeUnspecified: - return fmt.Errorf("unknown CaptureResultType: %d", cr.Type) - default: - return fmt.Errorf("unknown CaptureResultType: %d", cr.Type) - } -} - -// NewBinaryCaptureResult returns a binary capture result. -func NewBinaryCaptureResult(ts Timestamps, binaries []Binary) CaptureResult { - return CaptureResult{ - Timestamps: ts, - Type: CaptureTypeBinary, - Binaries: binaries, - } -} - -// NewTabularCaptureResultReadings returns a tabular readings result. -func NewTabularCaptureResultReadings(reqT time.Time, readings map[string]interface{}) (CaptureResult, error) { - var res CaptureResult - values, err := rprotoutils.ReadingGoToProto(readings) - if err != nil { - return res, err - } - - return CaptureResult{ - Timestamps: Timestamps{ - TimeRequested: reqT, - TimeReceived: time.Now(), - }, - Type: CaptureTypeTabular, - TabularData: TabularData{ - Payload: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "readings": structpb.NewStructValue(&structpb.Struct{Fields: values}), - }, - }, - }, - }, nil -} - -// NewTabularCaptureResult returns a tabular result. -func NewTabularCaptureResult(reqT time.Time, i interface{}) (CaptureResult, error) { - var res CaptureResult - readings, err := protoutils.StructToStructPbIgnoreOmitEmpty(i) - if err != nil { - return res, err - } - - return CaptureResult{ - Timestamps: Timestamps{ - TimeRequested: reqT, - TimeReceived: time.Now(), - }, - Type: CaptureTypeTabular, - TabularData: TabularData{ - Payload: readings, - }, - }, nil -} - -// ToProto converts a CaptureResult into a []*datasyncPB.SensorData{}. -func (cr CaptureResult) ToProto() []*datasyncPB.SensorData { - ts := cr.Timestamps - if td := cr.TabularData.Payload; td != nil { - return []*datasyncPB.SensorData{{ - Metadata: &datasyncPB.SensorMetadata{ - TimeRequested: timestamppb.New(ts.TimeRequested.UTC()), - TimeReceived: timestamppb.New(ts.TimeReceived.UTC()), - }, - Data: &datasyncPB.SensorData_Struct{ - Struct: td, - }, - }} - } - - var sd []*datasyncPB.SensorData - for _, b := range cr.Binaries { - sd = append(sd, &datasyncPB.SensorData{ - Metadata: &datasyncPB.SensorMetadata{ - TimeRequested: timestamppb.New(ts.TimeRequested.UTC()), - TimeReceived: timestamppb.New(ts.TimeReceived.UTC()), - MimeType: b.MimeType.ToProto(), - Annotations: b.Annotations.ToProto(), - }, - Data: &datasyncPB.SensorData_Binary{ - Binary: b.Payload, - }, - }) - } - return sd -} - -// MimeTypeFromProto converts a datasyncPB.MimeType to a data.MimeType. -func MimeTypeFromProto(mt datasyncPB.MimeType) MimeType { - switch mt { - case datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED: - return MimeTypeUnspecified - case datasyncPB.MimeType_MIME_TYPE_IMAGE_JPEG: - return MimeTypeImageJpeg - case datasyncPB.MimeType_MIME_TYPE_IMAGE_PNG: - return MimeTypeImagePng - case datasyncPB.MimeType_MIME_TYPE_APPLICATION_PCD: - return MimeTypeApplicationPcd - default: - return MimeTypeUnspecified - } -} - -// CameraFormatToMimeType converts a camera camerapb.Format into a MimeType. -func CameraFormatToMimeType(f camerapb.Format) MimeType { - switch f { - case camerapb.Format_FORMAT_UNSPECIFIED: - return MimeTypeUnspecified - case camerapb.Format_FORMAT_JPEG: - return MimeTypeImageJpeg - case camerapb.Format_FORMAT_PNG: - return MimeTypeImagePng - case camerapb.Format_FORMAT_RAW_RGBA: - fallthrough - case camerapb.Format_FORMAT_RAW_DEPTH: - fallthrough - default: - return MimeTypeUnspecified - } -} - -// MimeTypeToCameraFormat converts a data.MimeType into a camerapb.Format. -func MimeTypeToCameraFormat(mt MimeType) camerapb.Format { - if mt == MimeTypeImageJpeg { - return camerapb.Format_FORMAT_JPEG - } - - if mt == MimeTypeImagePng { - return camerapb.Format_FORMAT_PNG - } - return camerapb.Format_FORMAT_UNSPECIFIED -} - // FromDMContextKey is used to check whether the context is from data management. // Deprecated: use a camera.Extra with camera.NewContext instead. type FromDMContextKey struct{} @@ -351,6 +43,17 @@ var ErrNoCaptureToStore = status.Error(codes.FailedPrecondition, "no capture fro // If an error is ongoing, the frequency (in seconds) with which to suppress identical error logs. const identicalErrorLogFrequencyHz = 2 +// TabularDataBson is a denormalized sensor reading that can be +// encoded into BSON. +type TabularDataBson struct { + TimeRequested time.Time `bson:"time_requested"` + TimeReceived time.Time `bson:"time_received"` + ComponentName string `bson:"component_name"` + ComponentType string `bson:"component_type"` + MethodName string `bson:"method_name"` + Data bson.M `bson:"data"` +} + // Collector collects data to some target. type Collector interface { Close() @@ -604,17 +307,6 @@ func (c *collector) writeCaptureResults() { } } -// TabularDataBson is a denormalized sensor reading that can be -// encoded into BSON. -type TabularDataBson struct { - TimeRequested time.Time `bson:"time_requested"` - TimeReceived time.Time `bson:"time_received"` - ComponentName string `bson:"component_name"` - ComponentType string `bson:"component_type"` - MethodName string `bson:"method_name"` - Data bson.M `bson:"data"` -} - // maybeWriteToMongo will write to the mongoCollection // if it is non-nil and the msg is tabular data // logs errors on failure. diff --git a/data/collector_types.go b/data/collector_types.go new file mode 100644 index 00000000000..8b680918562 --- /dev/null +++ b/data/collector_types.go @@ -0,0 +1,394 @@ +package data + +import ( + "context" + "fmt" + "time" + + "github.com/pkg/errors" + dataPB "go.viam.com/api/app/data/v1" + datasyncPB "go.viam.com/api/app/datasync/v1" + camerapb "go.viam.com/api/component/camera/v1" + "go.viam.com/utils/protoutils" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + + rprotoutils "go.viam.com/rdk/protoutils" + rutils "go.viam.com/rdk/utils" +) + +// CaptureFunc allows the creation of simple Capturers with anonymous functions. +type CaptureFunc func(ctx context.Context, params map[string]*anypb.Any) (CaptureResult, error) + +// CaptureResult is the result of a capture function. +type CaptureResult struct { + // Type represents the type of result (binary or tabular) + Type CaptureType + // Timestamps contain the time the data was requested and received + Timestamps + // TabularData contains the tabular data payload when Type == CaptureResultTypeTabular + TabularData TabularData + // Binaries contains binary data responses when Type == CaptureResultTypeBinary + Binaries []Binary +} + +// BEGIN CONSTRUCTORS + +// NewBinaryCaptureResult returns a binary capture result. +func NewBinaryCaptureResult(ts Timestamps, binaries []Binary) CaptureResult { + return CaptureResult{ + Timestamps: ts, + Type: CaptureTypeBinary, + Binaries: binaries, + } +} + +// NewTabularCaptureResultReadings returns a tabular readings result. +func NewTabularCaptureResultReadings(reqT time.Time, readings map[string]interface{}) (CaptureResult, error) { + var res CaptureResult + values, err := rprotoutils.ReadingGoToProto(readings) + if err != nil { + return res, err + } + + return CaptureResult{ + Timestamps: Timestamps{ + TimeRequested: reqT, + TimeReceived: time.Now(), + }, + Type: CaptureTypeTabular, + TabularData: TabularData{ + Payload: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "readings": structpb.NewStructValue(&structpb.Struct{Fields: values}), + }, + }, + }, + }, nil +} + +// NewTabularCaptureResult returns a tabular result. +func NewTabularCaptureResult(reqT time.Time, i interface{}) (CaptureResult, error) { + var res CaptureResult + readings, err := protoutils.StructToStructPbIgnoreOmitEmpty(i) + if err != nil { + return res, err + } + + return CaptureResult{ + Timestamps: Timestamps{ + TimeRequested: reqT, + TimeReceived: time.Now(), + }, + Type: CaptureTypeTabular, + TabularData: TabularData{ + Payload: readings, + }, + }, nil +} + +// END CONSTRUCTORS + +// ToProto converts a CaptureResult into a []*datasyncPB.SensorData{}. +func (cr *CaptureResult) ToProto() []*datasyncPB.SensorData { + ts := cr.Timestamps + if td := cr.TabularData.Payload; td != nil { + return []*datasyncPB.SensorData{{ + Metadata: &datasyncPB.SensorMetadata{ + TimeRequested: timestamppb.New(ts.TimeRequested.UTC()), + TimeReceived: timestamppb.New(ts.TimeReceived.UTC()), + }, + Data: &datasyncPB.SensorData_Struct{ + Struct: td, + }, + }} + } + + var sd []*datasyncPB.SensorData + for _, b := range cr.Binaries { + sd = append(sd, &datasyncPB.SensorData{ + Metadata: &datasyncPB.SensorMetadata{ + TimeRequested: timestamppb.New(ts.TimeRequested.UTC()), + TimeReceived: timestamppb.New(ts.TimeReceived.UTC()), + MimeType: b.MimeType.ToProto(), + Annotations: b.Annotations.ToProto(), + }, + Data: &datasyncPB.SensorData_Binary{ + Binary: b.Payload, + }, + }) + } + return sd +} + +// Validate returns an error if the *CaptureResult is invalid. +func (cr *CaptureResult) Validate() error { + var ts Timestamps + if cr.Timestamps.TimeRequested == ts.TimeRequested { + return errors.New("Timestamps.TimeRequested must be set") + } + + if cr.Timestamps.TimeReceived == ts.TimeReceived { + return errors.New("Timestamps.TimeRequested must be set") + } + + switch cr.Type { + case CaptureTypeTabular: + if len(cr.Binaries) > 0 { + return errors.New("tabular result can't contain binary data") + } + if cr.TabularData.Payload == nil { + return errors.New("tabular result must have non empty tabular data") + } + return nil + case CaptureTypeBinary: + if cr.TabularData.Payload != nil { + return errors.New("binary result can't contain tabular data") + } + if len(cr.Binaries) == 0 { + return errors.New("binary result must have non empty binary data") + } + + for _, b := range cr.Binaries { + if len(b.Payload) == 0 { + return errors.New("binary results can't have empty binary payload") + } + } + return nil + case CaptureTypeUnspecified: + return fmt.Errorf("unknown CaptureResultType: %d", cr.Type) + default: + return fmt.Errorf("unknown CaptureResultType: %d", cr.Type) + } +} + +// CaptureType represents captured tabular or binary data. +type CaptureType int + +const ( + // CaptureTypeUnspecified represents that the data type of the captured data was not specified. + CaptureTypeUnspecified CaptureType = iota + // CaptureTypeTabular represents that the data type of the captured data is tabular. + CaptureTypeTabular + // CaptureTypeBinary represents that the data type of the captured data is binary. + CaptureTypeBinary +) + +// ToProto converts a DataType into a v1.DataType. +func (dt CaptureType) ToProto() datasyncPB.DataType { + switch dt { + case CaptureTypeTabular: + return datasyncPB.DataType_DATA_TYPE_TABULAR_SENSOR + case CaptureTypeBinary: + return datasyncPB.DataType_DATA_TYPE_BINARY_SENSOR + case CaptureTypeUnspecified: + return datasyncPB.DataType_DATA_TYPE_UNSPECIFIED + default: + return datasyncPB.DataType_DATA_TYPE_UNSPECIFIED + } +} + +// GetDataType returns the DataType of the method. +func GetDataType(methodName string) CaptureType { + switch methodName { + case nextPointCloud, readImage, pointCloudMap, GetImages: + return CaptureTypeBinary + default: + return CaptureTypeTabular + } +} + +// Timestamps are the timestamps the data was captured. +type Timestamps struct { + // TimeRequested represents the time the request for the data was started + TimeRequested time.Time + // TimeRequested represents the time the response for the request for the data + // was received + TimeReceived time.Time +} + +// MimeType represents the mime type of the sensor data. +type MimeType int + +// This follows the mime types supported in +// https://github.com/viamrobotics/api/pull/571/files#diff-b77927298d8d5d5228beeea47bd0860d9b322b4f3ef45e129bc238ec17704826R75 +const ( + // MimeTypeUnspecified means that the mime type was not specified. + MimeTypeUnspecified MimeType = iota + // MimeTypeImageJpeg means that the mime type is jpeg. + MimeTypeImageJpeg + // MimeTypeImagePng means that the mime type is png. + MimeTypeImagePng + // MimeTypeApplicationPcd means that the mime type is pcd. + MimeTypeApplicationPcd +) + +// ToProto converts MimeType to datasyncPB. +func (mt MimeType) ToProto() datasyncPB.MimeType { + switch mt { + case MimeTypeUnspecified: + return datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED + case MimeTypeImageJpeg: + return datasyncPB.MimeType_MIME_TYPE_IMAGE_JPEG + case MimeTypeImagePng: + return datasyncPB.MimeType_MIME_TYPE_IMAGE_PNG + case MimeTypeApplicationPcd: + return datasyncPB.MimeType_MIME_TYPE_APPLICATION_PCD + default: + return datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED + } +} + +// MimeTypeFromProto converts a datasyncPB.MimeType to a data.MimeType. +func MimeTypeFromProto(mt datasyncPB.MimeType) MimeType { + switch mt { + case datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED: + return MimeTypeUnspecified + case datasyncPB.MimeType_MIME_TYPE_IMAGE_JPEG: + return MimeTypeImageJpeg + case datasyncPB.MimeType_MIME_TYPE_IMAGE_PNG: + return MimeTypeImagePng + case datasyncPB.MimeType_MIME_TYPE_APPLICATION_PCD: + return MimeTypeApplicationPcd + default: + return MimeTypeUnspecified + } +} + +// CameraFormatToMimeType converts a camera camerapb.Format into a MimeType. +func CameraFormatToMimeType(f camerapb.Format) MimeType { + switch f { + case camerapb.Format_FORMAT_UNSPECIFIED: + return MimeTypeUnspecified + case camerapb.Format_FORMAT_JPEG: + return MimeTypeImageJpeg + case camerapb.Format_FORMAT_PNG: + return MimeTypeImagePng + case camerapb.Format_FORMAT_RAW_RGBA: + fallthrough + case camerapb.Format_FORMAT_RAW_DEPTH: + fallthrough + default: + return MimeTypeUnspecified + } +} + +// MimeTypeToCameraFormat converts a data.MimeType into a camerapb.Format. +func MimeTypeToCameraFormat(mt MimeType) camerapb.Format { + if mt == MimeTypeImageJpeg { + return camerapb.Format_FORMAT_JPEG + } + + if mt == MimeTypeImagePng { + return camerapb.Format_FORMAT_PNG + } + return camerapb.Format_FORMAT_UNSPECIFIED +} + +// Binary represents an element of a binary capture result response. +type Binary struct { + // Payload contains the binary payload + Payload []byte + // MimeType descibes the payload's MimeType + MimeType MimeType + // Annotations provide metadata about the Payload + Annotations Annotations +} + +// TabularData contains a tabular data payload. +type TabularData struct { + Payload *structpb.Struct +} + +// BoundingBox represents a labeled bounding box +// with an optional confidence interval between 0 and 1. +type BoundingBox struct { + Label string + Confidence *float64 + XMinNormalized float64 + YMinNormalized float64 + XMaxNormalized float64 + YMaxNormalized float64 +} + +// Classification represents a labeled classification +// with an optional confidence interval between 0 and 1. +type Classification struct { + Label string + Confidence *float64 +} + +// Annotations represents ML classifications. +type Annotations struct { + BoundingBoxes []BoundingBox + Classifications []Classification +} + +// Empty returns true when Annotations are empty. +func (mt Annotations) Empty() bool { + return len(mt.BoundingBoxes) == 0 && len(mt.Classifications) == 0 +} + +// ToProto converts Annotations to *dataPB.Annotations. +func (mt Annotations) ToProto() *dataPB.Annotations { + if mt.Empty() { + return nil + } + + var bboxes []*dataPB.BoundingBox + for _, bb := range mt.BoundingBoxes { + bboxes = append(bboxes, &dataPB.BoundingBox{ + Label: bb.Label, + Confidence: bb.Confidence, + XMinNormalized: bb.XMinNormalized, + XMaxNormalized: bb.XMaxNormalized, + YMinNormalized: bb.YMinNormalized, + YMaxNormalized: bb.YMaxNormalized, + }) + } + + var classifications []*dataPB.Classification + for _, c := range mt.Classifications { + classifications = append(classifications, &dataPB.Classification{ + Label: c.Label, + Confidence: c.Confidence, + }) + } + + return &dataPB.Annotations{ + Bboxes: bboxes, + Classifications: classifications, + } +} + +// getFileExt gets the file extension for a capture file. +func getFileExt(dataType CaptureType, methodName string, parameters map[string]string) string { + defaultFileExt := "" + switch dataType { + case CaptureTypeTabular: + return ".dat" + case CaptureTypeBinary: + if methodName == nextPointCloud { + return ".pcd" + } + if methodName == readImage { + // TODO: Add explicit file extensions for all mime types. + switch parameters["mime_type"] { + case rutils.MimeTypeJPEG: + return ".jpeg" + case rutils.MimeTypePNG: + return ".png" + case rutils.MimeTypePCD: + return ".pcd" + default: + return defaultFileExt + } + } + case CaptureTypeUnspecified: + return defaultFileExt + default: + return defaultFileExt + } + return defaultFileExt +}