diff --git a/.gitignore b/.gitignore index 6e90c2e59..be64a90e8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,16 +4,12 @@ # don't upload macOS folder info *.DS_Store -# don't upload the data directory -data/* - # don't upload node_modules from npm test node_modules/* package-lock.json -# don't upload the server config, as it will vary. -# (we still upload the example config) -config.yml - # potential files generated by golang bin/ + +# don't upload items directory +items/ diff --git a/.travis.yml b/.travis.yml index e7b5b47dd..02a8cee32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,17 @@ language: python +language: go python: - - "3.5" + - "3.6" +go: + - "1.10" +go_import_path: github.com/ucbdrive/sat before_install: - sudo apt-get update - sudo apt-get install -y npm - npm install -g eslint eslint-config-google install: - - pip install pycodestyle + - sudo pip install --upgrade pip + - sudo pip install pycodestyle - go get gopkg.in/yaml.v2 - npm install script: diff --git a/app/src/js/point_cloud/box3d.js b/app/src/js/point_cloud/box3d.js index 9b64d0b6d..b24eeaad3 100644 --- a/app/src/js/point_cloud/box3d.js +++ b/app/src/js/point_cloud/box3d.js @@ -13,89 +13,19 @@ function Box3d(sat, id) { Box3d.prototype = Object.create(SatLabel.prototype); -Box3d.prototype.color = function() { - let color = SatLabel.prototype.color.call(this); - let newColor = []; - for (let i = 0; i < color.length; i++) { - newColor.push(color[i] / 255.0); - } - return newColor; -}; - Box3d.prototype.setColor = function(hexColor, faces=null) { - let inputArray = typeof hexColor == 'object'; if (faces != null) { for (let i = 0; i < faces.length; i++) { - if (inputArray) { - this.box.geometry.faces[faces[i]].color.fromArray(hexColor); - } else { - this.box.geometry.faces[faces[i]].color.set(hexColor); - } + this.box.geometry.faces[faces[i]].color.setHex(hexColor); } } else { for (let i = 0; i < this.box.geometry.faces.length; i++) { - if (inputArray) { - this.box.geometry.faces[i].color.fromArray(hexColor); - } else { - this.box.geometry.faces[i].color.set(hexColor); - } + this.box.geometry.faces[i].color.setHex(hexColor); } } this.box.geometry.colorsNeedUpdate = true; }; -Box3d.prototype.createBox = function(position) { - let box = new THREE.Mesh( - new THREE.BoxGeometry( 1, 1, 1 ), - new THREE.MeshBasicMaterial({color: 0xffffff, - vertexColors: THREE.FaceColors, - transparent: true, - opacity: 0.5}) - ); - - let outline = new THREE.LineSegments( - new THREE.EdgesGeometry(box.geometry), - new THREE.LineBasicMaterial({color: 0xffffff})); - - box.outline = outline; - box.label = this; - - this.box = box; - - if (this.data) { - box.position.x = this.data['position'][0]; - box.position.y = this.data['position'][1]; - box.position.z = this.data['position'][2]; - box.outline.position.copy(box.position); - - box.rotation.x = this.data['rotation'][0]; - box.rotation.y = this.data['rotation'][1]; - box.rotation.z = this.data['rotation'][2]; - box.outline.rotation.copy(box.rotation); - - box.scale.x = this.data['scale'][0]; - box.scale.y = this.data['scale'][1]; - box.scale.z = this.data['scale'][2]; - box.outline.scale.copy(box.scale); - } else { - box.scale.z = 0.01; - - this.data = {}; - this.data['position'] = [position.x, position.y, position.z]; - this.data['rotation'] = [box.rotation.x, box.rotation.y, - box.rotation.z]; - this.data['scale'] = [box.scale.x, box.scale.y, box.scale.z]; - - box.position.copy(position); - box.outline.position.copy(box.position); - box.outline.scale.copy(box.scale); - } - - this.setColor(this.color()); - - return box; -}; - Box3d.prototype.moveBoxAlongViewPlane = function(viewPlaneNormal, viewPlaneOffset, boxMouseOverPoint, diff --git a/app/src/js/point_cloud/sat_point_cloud.js b/app/src/js/point_cloud/sat_point_cloud.js index 1d2b2d38e..6a6d85ea6 100644 --- a/app/src/js/point_cloud/sat_point_cloud.js +++ b/app/src/js/point_cloud/sat_point_cloud.js @@ -101,8 +101,6 @@ function SatPointCloud(sat, index, url) { this.EDITING = 2; this.selectionState = this.STANDBY; - this.SELECTION_COLOR = 0xff0000; - this.ADJUSTING_COLOR = 0xff8000; this.MOVING_BOX = 1; this.ROTATING_BOX = 2; @@ -581,7 +579,7 @@ SatPointCloud.prototype.handleMouseUp = function() { if (this.selectionState == this.EDITING) { this.selectionState = this.ADJUSTING; if (this.editState != this.MOVING_BOX) { - this.selectedLabel.setColor(this.ADJUSTING_COLOR); + this.selectedLabel.setColor(0xff0000); } this.editState = this.MOVING_BOX; } @@ -676,7 +674,6 @@ SatPointCloud.prototype.handleKeyDown = function(e) { } else if (this.selectionState == this.STANDBY) { if (this.selectedLabel != null) { this.selectionState = this.ADJUSTING; - this.selectedLabel.setColor(this.ADJUSTING_COLOR); } } break; @@ -709,20 +706,65 @@ SatPointCloud.prototype.handleKeyUp = function() { this.editState = this.MOVING_BOX; } if (this.selectionState == this.ADJUSTING) { - this.selectedLabel.setColor(this.ADJUSTING_COLOR); + this.selectedLabel.setColor(0xff0000); } }; SatPointCloud.prototype.addBoundingBox = function(label, select=false) { - let box = label.createBox(this.target); + let box = new THREE.Mesh( + new THREE.BoxGeometry( 1, 1, 1 ), + new THREE.MeshBasicMaterial({color: 0xffffff, + vertexColors: THREE.FaceColors, + transparent: true, + opacity: 0.5}) + ); + + let outline = new THREE.LineSegments( + new THREE.EdgesGeometry(box.geometry), + new THREE.LineBasicMaterial({color: 0xffffff})); + + box.outline = outline; + box.label = label; this.bounding_boxes.push(box); + + label.box = box; + + if (label.data) { + box.position.x = label.data['position'][0]; + box.position.y = label.data['position'][1]; + box.position.z = label.data['position'][2]; + box.outline.position.copy(box.position); + + box.rotation.x = label.data['rotation'][0]; + box.rotation.y = label.data['rotation'][1]; + box.rotation.z = label.data['rotation'][2]; + box.outline.rotation.copy(box.rotation); + + box.scale.x = label.data['scale'][0]; + box.scale.y = label.data['scale'][1]; + box.scale.z = label.data['scale'][2]; + box.outline.scale.copy(box.scale); + } else { + box.scale.z = 0.01; + + label.data = {}; + label.data['position'] = [this.target.x, this.target.y, + this.target.z]; + label.data['rotation'] = [box.rotation.x, box.rotation.y, + box.rotation.z]; + label.data['scale'] = [box.scale.x, box.scale.y, box.scale.z]; + + box.position.copy(this.target); + box.outline.position.copy(box.position); + box.outline.scale.copy(box.scale); + } + this.addLabelToList(label); if (select) { this.selectedLabelNewBox = true; this.select(label); this.selectionState = this.ADJUSTING; - label.setColor(this.ADJUSTING_COLOR); this._changeSelectedLabelCategory(); } @@ -809,7 +851,7 @@ SatPointCloud.prototype.select = function(label) { // If selecting same thing, then only deselect if (temp != label) { this.selectedLabel = label; - this.selectedLabel.setColor(this.SELECTION_COLOR); + this.selectedLabel.setColor(0xff0000); this.info_card.style.display = 'block'; @@ -886,7 +928,7 @@ SatPointCloud.prototype.select = function(label) { SatPointCloud.prototype.deselect = function() { if (this.selectedLabel != null) { - this.selectedLabel.setColor(this.selectedLabel.color()); + this.selectedLabel.setColor(0xffffff); this.selectedLabel = null; this.info_card.style.display = 'none'; this.deactivateLabelList(); @@ -969,7 +1011,7 @@ SatPointCloud.prototype.highlightMousedOverBox = function(mX, mY) { // Highlight current box if (intersects.length > 0) { this.boxMouseOver = intersects[0].object; - this.boxMouseOver.outline.material.color.set(this.SELECTION_COLOR); + this.boxMouseOver.outline.material.color.set(0xff0000); this.boxMouseOverPoint = intersects[0].point; } }; diff --git a/examples/bbox_attributes.yaml b/examples/bbox_attributes.yml similarity index 100% rename from examples/bbox_attributes.yaml rename to examples/bbox_attributes.yml diff --git a/server/go/export.go b/server/go/export.go new file mode 100644 index 000000000..8426682b8 --- /dev/null +++ b/server/go/export.go @@ -0,0 +1,244 @@ +package main + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strconv" +) + +type Seg2d struct { + Vertices [][]float64 `json:"vertices" yaml:"vertices"` + Types string `json:"types" yaml:"types"` + Closed bool `json:"closed" yaml:"closed"` +} + +type Box2d struct { + X1 float64 `json:"x1" yaml:"x1"` + X2 float64 `json:"x2" yaml:"x2"` + Y1 float64 `json:"y1" yaml:"y1"` + Y2 float64 `json:"y2" yaml:"y2"` +} + +// Download format specifications +type FileExport struct { + Name string `json:"name" yaml:"name"` + Attributes []map[string]interface{} `json:"attributes" yaml:"attributes"` + Items []ItemExport `json:"items" yaml:"items"` +} + +type ItemExport struct { + Timestamp int64 `json:"timestamp" yaml:"timestamp"` + Index int `json:"index" yaml:"index"` + Labels []LabelExport `json:"labels" yaml:"labels"` +} + +type LabelExport struct { + Id int `json:"id" yaml:"id"` + Category string `json:"category" yaml:"category"` + Attributes map[string]interface{} `json:"attributes" yaml:"attributes"` + Box2d Box2d `json:"box2d" yaml:"box2d"` + Seg2d []Seg2d `json:"seg2d" yaml:"seg2d"` + Box3d map[string]interface{} `json:"box3d" yaml:"box3d"` +} + +// structs for saved data +type VertexData struct { + Id int `json:"id" yaml:"id"` + X float64 `json:"x" yaml:"x"` + Y float64 `json:"y" yaml:"y"` + Type string `json:"type" yaml:"type"` +} + +type EdgeData struct { + Id int `json:"id" yaml:"id"` + Src int `json:"src" yaml:"src"` + Dest int `json:"dest" yaml:"dest"` + Type string `json:"type" yaml:"type"` + ControlPoints []VertexData `json:"control_points" yaml:"control_points"` +} + +type PolylineData struct { + Id int `json:"id" yaml:"id"` + Vertices []VertexData `json:"vertices" yaml:"vertices"` + Edges []EdgeData `json:"edges" yaml:"edges"` +} + +type Box2dData struct { + X float64 `json:"x" yaml:"x"` + Y float64 `json:"y" yaml:"y"` + W float64 `json:"w" yaml:"w"` + H float64 `json:"h" yaml:"h"` +} + +type Seg2dData struct { + Closed bool `json:"closed" yaml:"closed"` + Polys []PolylineData `json:"polys" yaml:"polys"` +} + +func MapToStruct(m map[string]interface{}, val interface{}) error { + tmp, err := json.Marshal(m) + if err != nil { + return err + } + err = json.Unmarshal(tmp, val) + if err != nil { + return err + } + return nil +} + +func ParseBox2d(data map[string]interface{}) Box2d { + _box2d := Box2dData{} + MapToStruct(data, &_box2d) + + box2d := Box2d{} + box2d.X1 = _box2d.X + box2d.Y1 = _box2d.Y + box2d.X2 = _box2d.X + _box2d.W + box2d.Y2 = _box2d.Y + _box2d.H + return box2d +} + +func ParseSeg2d(data map[string]interface{}) []Seg2d { + _seg2d := Seg2dData{} + MapToStruct(data, &_seg2d) + + seg2ds := []Seg2d{} + for _, _poly := range _seg2d.Polys { + poly := Seg2d{} + types := []byte{} + for i, vertex := range _poly.Vertices { + v_xy := []float64{vertex.X, vertex.Y} + poly.Vertices = append(poly.Vertices, v_xy) + types = append(types, 'L') + if i < len(_poly.Edges) && _poly.Edges[i].Type == "bezier" { + if (i < len(_poly.Edges)-1) || (_seg2d.Closed) { + for _, c := range _poly.Edges[i].ControlPoints { + c_xy := []float64{c.X, c.Y} + poly.Vertices = append(poly.Vertices, c_xy) + types = append(types, 'C') + } + } + } + } + poly.Closed = _seg2d.Closed + poly.Types = string(types[:]) + seg2ds = append(seg2ds, poly) + } + return seg2ds +} + +var floatType = reflect.TypeOf(float64(0)) +var integerType = reflect.TypeOf(int(0)) +var stringType = reflect.TypeOf("") + +func getFloatSlice(unk interface{}) ([]float64, error) { + if reflect.TypeOf(unk).Kind() != reflect.Slice { + return nil, fmt.Errorf("cannot convert interface to slice") + } + + v := reflect.ValueOf(unk) + array := make([]float64, v.Len()) + + for i := 0; i < v.Len(); i++ { + val, ok := v.Index(i).Interface().(float64) + if !ok { + return nil, fmt.Errorf("cannot convert interface to slice") + } + array[i] = val + } + + return array, nil +} + +func rotateXAxis3D(vector []float64, angle float64) error { + if len(vector) != 3 { + return fmt.Errorf("Input array was not 3 dimensional") + } + + y := vector[1] + z := vector[2] + + vector[1] = math.Cos(angle)*y - math.Sin(angle)*z + vector[2] = math.Sin(angle)*y + math.Cos(angle)*z + + return nil +} + +func rotateYAxis3D(vector []float64, angle float64) error { + if len(vector) != 3 { + return fmt.Errorf("Input array was not 3 dimensional") + } + + x := vector[0] + z := vector[2] + + vector[0] = math.Cos(angle)*x + math.Sin(angle)*z + vector[2] = -math.Sin(angle)*x + math.Cos(angle)*z + + return nil +} + +func rotateZAxis3D(vector []float64, angle float64) error { + if len(vector) != 3 { + return fmt.Errorf("Input array was not 3 dimensional") + } + + x := vector[0] + y := vector[1] + + vector[0] = math.Cos(angle)*x - math.Sin(angle)*y + vector[1] = math.Sin(angle)*x + math.Cos(angle)*y + + return nil +} + +func ParseBox3d(data map[string]interface{}) map[string]interface{} { + var box3d = map[string]interface{}{} + position, err := getFloatSlice(data["position"]) + rotation, err := getFloatSlice(data["rotation"]) + scale, err := getFloatSlice(data["scale"]) + if err != nil { + fmt.Println(err) + } + + fmt.Println(position) + fmt.Println(scale) + + // Initialize points + var points = [8][]float64{} + var ind = 0 + for x := float64(-0.5); x <= 0.5; x += 1 { + for y := float64(-0.5); y <= 0.5; y += 1 { + for z := float64(-0.5); z <= 0.5; z += 1 { + points[ind] = []float64{x, y, z} + ind++ + } + } + } + + // Modify scale, position, rotation and load into box3d + for i := 0; i < len(points); i++ { + var point = points[i] + if scale != nil { + point[0] *= scale[0] + point[1] *= scale[1] + point[2] *= scale[2] + } + if rotation != nil { + rotateXAxis3D(point, rotation[0]) + rotateYAxis3D(point, rotation[1]) + rotateZAxis3D(point, rotation[2]) + } + if position != nil { + point[0] += position[0] + point[1] += position[1] + point[2] += position[2] + } + box3d["p"+strconv.Itoa(i)] = point + } + + return box3d +} diff --git a/server/go/export/export.go b/server/go/export/export.go deleted file mode 100644 index 26f96485d..000000000 --- a/server/go/export/export.go +++ /dev/null @@ -1,244 +0,0 @@ -package export - -import ( - "encoding/json" - "math" - "reflect" - "fmt" - "strconv" -) - -type Seg2d struct { - Vertices [][]float64 `json:"vertices" yaml:"vertices"` - Types string `json:"types" yaml:"types"` - Closed bool `json:"closed" yaml:"closed"` -} - -type Box2d struct { - X1 float64 `json:"x1" yaml:"x1"` - X2 float64 `json:"x2" yaml:"x2"` - Y1 float64 `json:"y1" yaml:"y1"` - Y2 float64 `json:"y2" yaml:"y2"` -} - -// Download format specifications -type ExportFile struct { - Name string `json:"name" yaml:"name"` - Attributes []map[string]interface{} `json:"attributes" yaml:"attributes"` - Items []Item `json:"items" yaml:"items"` -} - -type Item struct { - Timestamp int64 `json:"timestamp" yaml:"timestamp"` - Index int `json:"index" yaml:"index"` - Labels []Label `json:"labels" yaml:"labels"` -} - -type Label struct { - Id int `json:"id" yaml:"id"` - Category string `json:"category" yaml:"category"` - Attributes map[string]interface{} `json:"attributes" yaml:"attributes"` - Box2d Box2d `json:"box2d" yaml:"box2d"` - Seg2d []Seg2d `json:"seg2d" yaml:"seg2d"` - Box3d map[string]interface{} `json:"box3d" yaml:"box3d"` -} - -// structs for saved data -type VertexData struct { - Id int `json:"id" yaml:"id"` - X float64 `json:"x" yaml:"x"` - Y float64 `json:"y" yaml:"y"` - Type string `json:"type" yaml:"type"` -} - -type EdgeData struct { - Id int `json:"id" yaml:"id"` - Src int `json:"src" yaml:"src"` - Dest int `json:"dest" yaml:"dest"` - Type string `json:"type" yaml:"type"` - ControlPoints []VertexData `json:"control_points" yaml:"control_points"` -} - -type PolylineData struct { - Id int `json:"id" yaml:"id"` - Vertices []VertexData `json:"vertices" yaml:"vertices"` - Edges []EdgeData `json:"edges" yaml:"edges"` -} - -type Box2dData struct { - X float64 `json:"x" yaml:"x"` - Y float64 `json:"y" yaml:"y"` - W float64 `json:"w" yaml:"w"` - H float64 `json:"h" yaml:"h"` -} - -type Seg2dData struct { - Closed bool `json:"closed" yaml:"closed"` - Polys []PolylineData `json:"polys" yaml:"polys"` -} - -func MapToStruct(m map[string]interface{}, val interface{}) error { - tmp, err := json.Marshal(m) - if err != nil { - return err - } - err = json.Unmarshal(tmp, val) - if err != nil { - return err - } - return nil -} - -func ParseBox2d(data map[string]interface{}) (Box2d) { - _box2d := Box2dData{} - MapToStruct(data, &_box2d) - - box2d := Box2d{} - box2d.X1 = _box2d.X - box2d.Y1 = _box2d.Y - box2d.X2 = _box2d.X + _box2d.W - box2d.Y2 = _box2d.Y + _box2d.H - return box2d -} - -func ParseSeg2d(data map[string]interface{}) ([]Seg2d) { - _seg2d := Seg2dData{} - MapToStruct(data, &_seg2d) - - seg2ds := []Seg2d{} - for _, _poly := range _seg2d.Polys { - poly := Seg2d{} - types := []byte{} - for i, vertex := range _poly.Vertices { - v_xy := []float64{vertex.X, vertex.Y} - poly.Vertices = append(poly.Vertices, v_xy) - types = append(types, 'L') - if i < len(_poly.Edges) && _poly.Edges[i].Type == "bezier" { - if (i < len(_poly.Edges) - 1) || (_seg2d.Closed) { - for _, c := range _poly.Edges[i].ControlPoints { - c_xy := []float64{c.X, c.Y} - poly.Vertices = append(poly.Vertices, c_xy) - types = append(types, 'C') - } - } - } - } - poly.Closed = _seg2d.Closed - poly.Types = string(types[:]) - seg2ds = append(seg2ds, poly) - } - return seg2ds -} - -var floatType = reflect.TypeOf(float64(0)) -var integerType = reflect.TypeOf(int(0)) -var stringType = reflect.TypeOf("") - -func getFloatSlice(unk interface{}) ([]float64, error) { - if (reflect.TypeOf(unk).Kind() != reflect.Slice) { - return nil, fmt.Errorf("cannot convert interface to slice") - } - - v := reflect.ValueOf(unk) - array := make([]float64, v.Len()) - - for i := 0; i < v.Len(); i++ { - val, ok := v.Index(i).Interface().(float64) - if !ok { - return nil, fmt.Errorf("cannot convert interface to slice") - } - array[i] = val - } - - return array, nil -} - -func rotateXAxis3D(vector []float64, angle float64) (error) { - if len(vector) != 3 { - return fmt.Errorf("Input array was not 3 dimensional") - } - - y := vector[1] - z := vector[2] - - vector[1] = math.Cos(angle) * y - math.Sin(angle) * z - vector[2] = math.Sin(angle) * y + math.Cos(angle) * z - - return nil -} - -func rotateYAxis3D(vector []float64, angle float64) (error) { - if len(vector) != 3 { - return fmt.Errorf("Input array was not 3 dimensional") - } - - x := vector[0] - z := vector[2] - - vector[0] = math.Cos(angle) * x + math.Sin(angle) * z - vector[2] = -math.Sin(angle) * x + math.Cos(angle) * z - - return nil -} - -func rotateZAxis3D(vector []float64, angle float64) (error) { - if len(vector) != 3 { - return fmt.Errorf("Input array was not 3 dimensional") - } - - x := vector[0] - y := vector[1] - - vector[0] = math.Cos(angle) * x - math.Sin(angle) * y - vector[1] = math.Sin(angle) * x + math.Cos(angle) * y - - return nil -} - -func ParseBox3d(data map[string]interface{}) (map[string]interface{}) { - var box3d = map[string]interface{}{} - position, err := getFloatSlice(data["position"]) - rotation, err := getFloatSlice(data["rotation"]) - scale, err := getFloatSlice(data["scale"]) - if err != nil { - fmt.Println(err) - } - - fmt.Println(position) - fmt.Println(scale) - - // Initialize points - var points = [8][]float64{}; - var ind = 0 - for x := float64(-0.5); x <= 0.5; x += 1 { - for y := float64(-0.5); y <= 0.5; y += 1 { - for z := float64(-0.5); z <= 0.5; z += 1 { - points[ind] = []float64{x, y, z}; - ind++; - } - } - } - - // Modify scale, position, rotation and load into box3d - for i := 0; i < len(points); i++ { - var point = points[i] - if scale != nil { - point[0] *= scale[0] - point[1] *= scale[1] - point[2] *= scale[2] - } - if rotation != nil { - rotateXAxis3D(point, rotation[0]) - rotateYAxis3D(point, rotation[1]) - rotateZAxis3D(point, rotation[2]) - } - if position != nil { - point[0] += position[0] - point[1] += position[1] - point[2] += position[2] - } - box3d["p" + strconv.Itoa(i)] = point - } - - return box3d -} diff --git a/server/go/export/export_test.go b/server/go/export_test.go similarity index 89% rename from server/go/export/export_test.go rename to server/go/export_test.go index ee013866e..5c29ddaef 100644 --- a/server/go/export/export_test.go +++ b/server/go/export_test.go @@ -1,36 +1,36 @@ -package export +package main + import ( - "testing" "math" + "testing" ) -func FloatEqual(f1 float64, f2 float64) (bool) { - if math.Abs(f1 - f2) < 0.001 { +func FloatEqual(f1 float64, f2 float64) bool { + if math.Abs(f1-f2) < 0.001 { return true } return false } -func FloatArrayEqual(vertices1 []float64, vertices2 []float64) (bool) { +func FloatArrayEqual(vertices1 []float64, vertices2 []float64) bool { for i := 0; i < len(vertices1); i++ { if !FloatEqual(vertices1[i], vertices2[i]) { - return false; + return false } } - return true; + return true } -func FloatArrayOfArrayEqual(vertices1 [][]float64, vertices2 [][]float64) (bool) { +func FloatArrayOfArrayEqual(vertices1 [][]float64, vertices2 [][]float64) bool { for i := 0; i < len(vertices1); i++ { if !FloatArrayEqual(vertices1[i], vertices2[i]) { - return false; + return false } } - return true; + return true } - -func coords(data map[string]interface{}) ([]float64) { +func coords(data map[string]interface{}) []float64 { v := VertexData{} MapToStruct(data, &v) return []float64{v.X, v.Y} @@ -39,19 +39,19 @@ func coords(data map[string]interface{}) ([]float64) { // data before parsing var Box2dDataStructs = []map[string]interface{}{ - {"x": 478.7920184573, "y": 454.5838057954, "W": 173.1446443451, "H": 24.6506756705,}, - {"x": 910.1114888987, "y": 699.7739185242, "W": 22.6105225649, "H": 79.6625312940,}, - {"x": 857.6822839050, "y": 62.5223033165, "W": 151.5271330284, "H": 174.6097113707,}, - {"x": 760.7877908042, "y": 201.4584124542, "W": 1.1209293956, "H": 142.2690489844,}, - {"x": 587.6507607175, "y": 752.4520039331, "W": 132.3780017789, "H": 186.3163234249,}, - {"x": 801.7192731172, "y": 46.0775615369, "W": 59.0162523807, "H": 86.5581569625,}, - {"x": 29.8084527376, "y": 571.1304971125, "W": 198.8522393520, "H": 110.7235428664,}, - {"x": 867.4728288827, "y": 442.8921109871, "W": 157.2019004174, "H": 165.4684783084,}, - {"x": 567.0276746260, "y": 598.5596172189, "W": 68.1508588984, "H": 4.1495718779,}, - {"x": 965.0563346591, "y": 636.1565779043, "W": 147.9695805767, "H": 20.8757090088,}, + {"x": 478.7920184573, "y": 454.5838057954, "W": 173.1446443451, "H": 24.6506756705}, + {"x": 910.1114888987, "y": 699.7739185242, "W": 22.6105225649, "H": 79.6625312940}, + {"x": 857.6822839050, "y": 62.5223033165, "W": 151.5271330284, "H": 174.6097113707}, + {"x": 760.7877908042, "y": 201.4584124542, "W": 1.1209293956, "H": 142.2690489844}, + {"x": 587.6507607175, "y": 752.4520039331, "W": 132.3780017789, "H": 186.3163234249}, + {"x": 801.7192731172, "y": 46.0775615369, "W": 59.0162523807, "H": 86.5581569625}, + {"x": 29.8084527376, "y": 571.1304971125, "W": 198.8522393520, "H": 110.7235428664}, + {"x": 867.4728288827, "y": 442.8921109871, "W": 157.2019004174, "H": 165.4684783084}, + {"x": 567.0276746260, "y": 598.5596172189, "W": 68.1508588984, "H": 4.1495718779}, + {"x": 965.0563346591, "y": 636.1565779043, "W": 147.9695805767, "H": 20.8757090088}, } -var Vertices = []map[string]interface{} { +var Vertices = []map[string]interface{}{ {"id": 100, "x": 352.9180483839, "y": 48.0398192532, "type": "vertex"}, {"id": 101, "x": 438.5795776342, "y": 194.3305046791, "type": "vertex"}, {"id": 102, "x": 151.8652725570, "y": 554.5075538404, "type": "vertex"}, @@ -62,10 +62,9 @@ var Vertices = []map[string]interface{} { {"id": 107, "x": 525.8007402697, "y": 14.5254923717, "type": "vertex"}, {"id": 108, "x": 975.7559088323, "y": 326.6811136843, "type": "vertex"}, {"id": 109, "x": 9.9677230648, "y": 360.4314506879, "type": "vertex"}, - } -var ControlPoints = []map[string]interface{} { +var ControlPoints = []map[string]interface{}{ {"id": 200, "x": 976.2323493915, "y": 56.0409970319, "type": "control_point"}, {"id": 201, "x": 221.3166425928, "y": 448.9519881560, "type": "control_point"}, {"id": 202, "x": 49.3708157221, "y": 633.6111675987, "type": "control_point"}, @@ -98,11 +97,11 @@ var PolylineDataStructs = []map[string]interface{}{ }, "Edges": []map[string]interface{}{ {"id": 300, "src": Vertices[0]["id"], "dest": Vertices[1]["id"], "type": "bezier", - "control_points": []map[string]interface{}{ControlPoints[0], ControlPoints[1],}}, + "control_points": []map[string]interface{}{ControlPoints[0], ControlPoints[1]}}, {"id": 301, "src": Vertices[1]["id"], "dest": Vertices[2]["id"], "type": "bezier", - "control_points": []map[string]interface{}{ControlPoints[2], ControlPoints[3],}}, + "control_points": []map[string]interface{}{ControlPoints[2], ControlPoints[3]}}, {"id": 302, "src": Vertices[2]["id"], "dest": Vertices[0]["id"], "type": "bezier", - "control_points": []map[string]interface{}{ControlPoints[4], ControlPoints[5],}}, + "control_points": []map[string]interface{}{ControlPoints[4], ControlPoints[5]}}, }, }, @@ -114,21 +113,21 @@ var PolylineDataStructs = []map[string]interface{}{ "Edges": []map[string]interface{}{ {"id": 300, "src": Vertices[1]["id"], "dest": Vertices[4]["id"], "type": "line", "control_points": nil}, {"id": 301, "src": Vertices[4]["id"], "dest": Vertices[2]["id"], "type": "bezier", - "control_points": []map[string]interface{}{ControlPoints[0], ControlPoints[1],}}, + "control_points": []map[string]interface{}{ControlPoints[0], ControlPoints[1]}}, {"id": 302, "src": Vertices[2]["id"], "dest": Vertices[8]["id"], "type": "line", "control_points": nil}, {"id": 303, "src": Vertices[8]["id"], "dest": Vertices[5]["id"], "type": "bezier", - "control_points": []map[string]interface{}{ControlPoints[2], ControlPoints[3],}}, + "control_points": []map[string]interface{}{ControlPoints[2], ControlPoints[3]}}, {"id": 304, "src": Vertices[5]["id"], "dest": Vertices[7]["id"], "type": "bezier", - "control_points": []map[string]interface{}{ControlPoints[4], ControlPoints[5],}}, + "control_points": []map[string]interface{}{ControlPoints[4], ControlPoints[5]}}, {"id": 305, "src": Vertices[7]["id"], "dest": Vertices[9]["id"], "type": "line", "control_points": nil}, {"id": 306, "src": Vertices[9]["id"], "dest": Vertices[6]["id"], "type": "line", "control_points": nil}, {"id": 307, "src": Vertices[6]["id"], "dest": Vertices[3]["id"], "type": "bezier", - "control_points": []map[string]interface{}{ControlPoints[6], ControlPoints[7],}}, + "control_points": []map[string]interface{}{ControlPoints[6], ControlPoints[7]}}, {"id": 308, "src": Vertices[3]["id"], "dest": Vertices[0]["id"], "type": "line", "control_points": nil}, {"id": 309, "src": Vertices[0]["id"], "dest": Vertices[1]["id"], "type": "bezier", "control_points": []map[string]interface{}{ - ControlPoints[8], ControlPoints[9], - }}, + ControlPoints[8], ControlPoints[9], + }}, }, }, } @@ -152,19 +151,19 @@ var PathSeg2dDataStructs = []map[string]interface{}{ // data after parsing var Box2dStructs = []Box2d{ - {478.7920184573, 651.9366628024, 454.5838057954, 479.2344814658,}, - {910.1114888987, 932.7220114636, 699.7739185242, 779.4364498182,}, - {857.6822839050, 1009.2094169334, 62.5223033165, 237.1320146872,}, - {760.7877908042, 761.9087201998, 201.4584124542, 343.7274614386,}, - {587.6507607175, 720.0287624964, 752.4520039331, 938.7683273580,}, - {801.7192731172, 860.7355254979, 46.0775615369, 132.6357184994,}, - {29.8084527376, 228.6606920896, 571.1304971125, 681.8540399789,}, - {867.4728288827, 1024.6747293001, 442.8921109871, 608.3605892955,}, - {567.0276746260, 635.1785335244, 598.5596172189, 602.7091890968,}, - {965.0563346591, 1113.0259152359, 636.1565779043, 657.0322869132,}, + {478.7920184573, 651.9366628024, 454.5838057954, 479.2344814658}, + {910.1114888987, 932.7220114636, 699.7739185242, 779.4364498182}, + {857.6822839050, 1009.2094169334, 62.5223033165, 237.1320146872}, + {760.7877908042, 761.9087201998, 201.4584124542, 343.7274614386}, + {587.6507607175, 720.0287624964, 752.4520039331, 938.7683273580}, + {801.7192731172, 860.7355254979, 46.0775615369, 132.6357184994}, + {29.8084527376, 228.6606920896, 571.1304971125, 681.8540399789}, + {867.4728288827, 1024.6747293001, 442.8921109871, 608.3605892955}, + {567.0276746260, 635.1785335244, 598.5596172189, 602.7091890968}, + {965.0563346591, 1113.0259152359, 636.1565779043, 657.0322869132}, } -var PolygonStructs = []Seg2d { +var PolygonStructs = []Seg2d{ { [][]float64{ coords(Vertices[0]), @@ -219,7 +218,7 @@ var PolygonStructs = []Seg2d { }, } -var PathStructs = []Seg2d { +var PathStructs = []Seg2d{ { [][]float64{ coords(Vertices[0]), @@ -290,9 +289,9 @@ func TestBox2d(t *testing.T) { for i := 0; i < len(Box2dDataStructs); i++ { box2dConverted := ParseBox2d(Box2dDataStructs[i]) allEqual := FloatEqual(box2dConverted.X1, Box2dStructs[i].X1) && - FloatEqual(box2dConverted.X2, Box2dStructs[i].X2) && - FloatEqual(box2dConverted.Y1, Box2dStructs[i].Y1) && - FloatEqual(box2dConverted.Y2, Box2dStructs[i].Y2) + FloatEqual(box2dConverted.X2, Box2dStructs[i].X2) && + FloatEqual(box2dConverted.Y1, Box2dStructs[i].Y1) && + FloatEqual(box2dConverted.Y2, Box2dStructs[i].Y2) if !allEqual { // error! @@ -333,7 +332,6 @@ func TestPolygonSeg2d(t *testing.T) { } } - func TestPathSeg2d(t *testing.T) { for i := 0; i < len(PathSeg2dDataStructs); i++ { seg2dConverted := ParseSeg2d(PathSeg2dDataStructs[i]) @@ -361,4 +359,4 @@ func TestPathSeg2d(t *testing.T) { } } } -} \ No newline at end of file +} diff --git a/server/go/main.go b/server/go/main.go index 41abc9907..5a6a9dbcd 100644 --- a/server/go/main.go +++ b/server/go/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "gopkg.in/yaml.v2" "io" "io/ioutil" @@ -9,7 +10,6 @@ import ( "net/http" "os" "path" - "fmt" ) var ( @@ -65,7 +65,7 @@ func (env Env) PointCloudPath() string { } func Init( -// Initialize all the loggers + // Initialize all the loggers traceHandle io.Writer, infoHandle io.Writer, warningHandle io.Writer, diff --git a/server/go/sat.go b/server/go/sat.go index 192110b78..7f52260f2 100644 --- a/server/go/sat.go +++ b/server/go/sat.go @@ -3,17 +3,17 @@ package main import ( "bytes" "encoding/json" + "errors" "gopkg.in/yaml.v2" "html/template" "io" "io/ioutil" + "log" "net/http" "net/url" "os" "path" "strconv" - "log" - "./export" ) // A collection of Items to be split into Tasks. Represents one unified type @@ -50,16 +50,16 @@ func (project *Project) Save() { // Info about a Project shared by Project and Task. type ProjectOptions struct { - Name string `json:"name" yaml:"name"` - ItemType string `json:"itemType" yaml:"itemType"` - LabelType string `json:"labelType" yaml:"labelType"` - TaskSize int `json:"taskSize" yaml:"taskSize"` - HandlerUrl string `json:"handlerUrl" yaml:"handlerUrl"` - PageTitle string `json:"pageTitle" yaml:"pageTitle"` - Categories []Category `json:"categories" yaml:"categories"` - NumLeafCategories int `json:"numLeafCategories" yaml:"numLeafCategories"` - Attributes []Attribute `json:"attributes" yaml:"attributes"` - VideoMetaData VideoMetaData `json:"metadata" yaml:"metadata"` + Name string `json:"name" yaml:"name"` + ItemType string `json:"itemType" yaml:"itemType"` + LabelType string `json:"labelType" yaml:"labelType"` + TaskSize int `json:"taskSize" yaml:"taskSize"` + HandlerUrl string `json:"handlerUrl" yaml:"handlerUrl"` + PageTitle string `json:"pageTitle" yaml:"pageTitle"` + Categories []Category `json:"categories" yaml:"categories"` + NumLeafCategories int `json:"numLeafCategories" yaml:"numLeafCategories"` + Attributes []Attribute `json:"attributes" yaml:"attributes"` + VideoMetaData VideoMetaData `json:"metadata" yaml:"metadata"` } // A workably-sized collection of Items belonging to a Project. @@ -209,16 +209,16 @@ type DashboardContents struct { } type TaskURL struct { - URL string `json:"url" yaml:"url"` + URL string `json:"url" yaml:"url"` } // unescaped marshal used to encode url string func JSONMarshal(t interface{}) ([]byte, error) { - buffer := &bytes.Buffer{} - encoder := json.NewEncoder(buffer) - encoder.SetEscapeHTML(false) - err := encoder.Encode(t) - return buffer.Bytes(), err + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + err := encoder.Encode(t) + return buffer.Bytes(), err } // Function type for handlers @@ -250,15 +250,15 @@ func WrapHandleFunc(fn HandleFunc) HandleFunc { } func countCategories(categories []Category) int { - count := 0 - for _, category := range categories { - if len(category.Subcategories) > 0 { - count += countCategories(category.Subcategories) - } else { - count += 1 - } - } - return count + count := 0 + for _, category := range categories { + if len(category.Subcategories) > 0 { + count += countCategories(category.Subcategories) + } else { + count += 1 + } + } + return count } func dashboardHandler(w http.ResponseWriter, r *http.Request) { @@ -270,13 +270,14 @@ func dashboardHandler(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - projectName := r.FormValue("project_name") - dashboardContents := DashboardContents{ - Project: GetProject(projectName), - Tasks: GetTasksInProject(projectName), + dashboardContents, err := GetDashboardContents(r.FormValue("project_name")) + Info.Println(dashboardContents.Tasks) + if err != nil { + Error.Println(err) + } else { + Info.Println(dashboardContents.Tasks) // project is too verbose to log + tmpl.Execute(w, dashboardContents) } - Info.Println(dashboardContents.Tasks) // project is too verbose to log - tmpl.Execute(w, dashboardContents) } func vendorHandler(w http.ResponseWriter, r *http.Request) { @@ -286,13 +287,13 @@ func vendorHandler(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - projectName := r.FormValue("project_name") - dashboardContents := DashboardContents{ - Project: GetProject(projectName), - Tasks: GetTasksInProject(projectName), + dashboardContents, err := GetDashboardContents(r.FormValue("project_name")) + if err != nil { + Error.Println(err) + } else { + Info.Println(dashboardContents.Tasks) // project is too verbose to log + tmpl.Execute(w, dashboardContents) } - Info.Println(dashboardContents.Tasks) // project is too verbose to log - tmpl.Execute(w, dashboardContents) } // Handles the posting of new projects @@ -303,8 +304,9 @@ func postProjectHandler(w http.ResponseWriter, r *http.Request) { } // validate form fields that are required - if !formValidation(w, r) { - return + err := formValidation(w, r) + if err != nil { + Error.Println(err) } // make sure the project name in the form is new @@ -342,7 +344,7 @@ func postProjectHandler(w http.ResponseWriter, r *http.Request) { // get the vendor ID from form vendorId, err := strconv.Atoi(r.FormValue("vendor_id")) if err != nil { - if (r.FormValue("vendor_id") == "") { + if r.FormValue("vendor_id") == "" { vendorId = -1 } else { Error.Println(err) @@ -355,16 +357,16 @@ func postProjectHandler(w http.ResponseWriter, r *http.Request) { // initialize and save the project var projectOptions = ProjectOptions{ - Name: projectName, - ItemType: itemType, - LabelType: labelType, - TaskSize: taskSize, - HandlerUrl: handlerUrl, - PageTitle: pageTitle, - Categories: categories, - NumLeafCategories: numLeafCategories, - Attributes: attributes, - VideoMetaData: videoMetaData, + Name: projectName, + ItemType: itemType, + LabelType: labelType, + TaskSize: taskSize, + HandlerUrl: handlerUrl, + PageTitle: pageTitle, + Categories: categories, + NumLeafCategories: numLeafCategories, + Attributes: attributes, + VideoMetaData: videoMetaData, } var project = Project{ Items: items, @@ -383,16 +385,24 @@ func executeLabelingTemplate(w http.ResponseWriter, r *http.Request, tmpl *templ // get task name from the URL projectName := r.URL.Query()["project_name"][0] taskIndex := r.URL.Query()["task_index"][0] - var assignment Assignment - if (!Exists(path.Join(env.DataDir, projectName, "assignments", - taskIndex, DEFAULT_WORKER+".json"))) { + if !Exists(path.Join(env.DataDir, projectName, "assignments", + taskIndex, DEFAULT_WORKER+".json")) { // if assignment does not exist, create it - assignment = CreateAssignment(projectName, taskIndex, DEFAULT_WORKER) + assignment, err := CreateAssignment(projectName, taskIndex, DEFAULT_WORKER) + if err != nil { + Error.Println(err) + return + } + tmpl.Execute(w, assignment) } else { // otherwise, get that assignment - assignment = GetAssignment(projectName, taskIndex, DEFAULT_WORKER) + assignment, err := GetAssignment(projectName, taskIndex, DEFAULT_WORKER) + if err != nil { + Error.Println(err) + return + } + tmpl.Execute(w, assignment) } - tmpl.Execute(w, assignment) } // Handles the loading of an assignment given its project name, task index, and worker ID. @@ -409,15 +419,23 @@ func postLoadAssignmentHandler(w http.ResponseWriter, r *http.Request) { projectName := assignmentToLoad.Task.ProjectOptions.Name taskIndex := strconv.Itoa(assignmentToLoad.Task.Index) var loadedAssignment Assignment - if (!Exists(path.Join(env.DataDir, projectName, "assignments", taskIndex, - DEFAULT_WORKER+".json"))) { + if !Exists(path.Join(env.DataDir, projectName, "assignments", taskIndex, + DEFAULT_WORKER+".json")) { // if assignment does not exist, create it // TODO: resolve tension between this function and executeLabelingTemplate() - loadedAssignment = CreateAssignment(projectName, taskIndex, + loadedAssignment, err = CreateAssignment(projectName, taskIndex, DEFAULT_WORKER) + if err != nil { + Error.Println(err) + return + } } else { - loadedAssignment = GetAssignment(projectName, taskIndex, + loadedAssignment, err = GetAssignment(projectName, taskIndex, DEFAULT_WORKER) + if err != nil { + Error.Println(err) + return + } loadedAssignment.StartTime = recordTimestamp() } loadedAssignmentJson, err := json.Marshal(loadedAssignment) @@ -461,73 +479,85 @@ func postSaveHandler(w http.ResponseWriter, r *http.Request) { // Handles the export of submitted assignments func postExportHandler(w http.ResponseWriter, r *http.Request) { - exportFile := export.ExportFile{} - var projectName = r.FormValue("project_name") - projectFilePath := path.Join(env.DataDir, projectName, "project.json") - projectFileContents, err := ioutil.ReadFile(projectFilePath) - if err != nil { - Error.Println(err) - } + exportFile := FileExport{} + var projectName = r.FormValue("project_name") + projectFilePath := path.Join(env.DataDir, projectName, "project.json") + projectFileContents, err := ioutil.ReadFile(projectFilePath) + if err != nil { + Error.Println(err) + } - projectToLoad := Project{} - err = json.Unmarshal(projectFileContents, &projectToLoad) - if err != nil { - Error.Println(err) - } - exportFile.Name = projectToLoad.Options.Name - // exportFile.Categories = projectToLoad.Options.Categories - // exportFile.Attributes = projectToLoad.Options.Attributes + projectToLoad := Project{} + err = json.Unmarshal(projectFileContents, &projectToLoad) + if err != nil { + Error.Println(err) + } + exportFile.Name = projectToLoad.Options.Name + // exportFile.Categories = projectToLoad.Options.Categories + // exportFile.Attributes = projectToLoad.Options.Attributes - // Grab the latest submissions from all tasks - tasks := GetTasksInProject(projectName) - for _, task := range tasks { - latestSubmission := GetAssignment(projectName, strconv.Itoa(task.Index), DEFAULT_WORKER) - for _, itemToLoad := range latestSubmission.Task.Items { - item := export.Item{} - item.Timestamp = 10000 // to be fixed - item.Index = itemToLoad.Index - for _, labelId := range itemToLoad.LabelIds { - var labelToLoad Label - for _, label := range latestSubmission.Labels { - if label.Id == labelId { - labelToLoad = label - break - } - } - label := export.Label{} - label.Id = labelId - label.Category = labelToLoad.CategoryPath - label.Attributes = labelToLoad.Attributes - switch projectToLoad.Options.LabelType { - case "box2d": - label.Box2d = export.ParseBox2d(labelToLoad.Data) - case "box3d": - label.Box3d = export.ParseBox3d(labelToLoad.Data) - case "segmentation": - label.Seg2d = export.ParseSeg2d(labelToLoad.Data) - case "lane": - label.Seg2d = export.ParseSeg2d(labelToLoad.Data) - } - item.Labels = append(item.Labels, label) - } - exportFile.Items = append(exportFile.Items, item) - } - } + // Grab the latest submissions from all tasks + tasks, err := GetTasksInProject(projectName) + if err != nil { + Error.Println(err) + return + } + for _, task := range tasks { + latestSubmission, err := GetAssignment(projectName, strconv.Itoa(task.Index), DEFAULT_WORKER) + if err != nil { + Error.Println(err) + return + } + for _, itemToLoad := range latestSubmission.Task.Items { + item := ItemExport{} + item.Timestamp = 10000 // to be fixed + item.Index = itemToLoad.Index + for _, labelId := range itemToLoad.LabelIds { + var labelToLoad Label + for _, label := range latestSubmission.Labels { + if label.Id == labelId { + labelToLoad = label + break + } + } + label := LabelExport{} + label.Id = labelId + label.Category = labelToLoad.CategoryPath + label.Attributes = labelToLoad.Attributes + switch projectToLoad.Options.LabelType { + case "box2d": + label.Box2d = ParseBox2d(labelToLoad.Data) + case "box3d": + label.Box3d = ParseBox3d(labelToLoad.Data) + case "segmentation": + label.Seg2d = ParseSeg2d(labelToLoad.Data) + case "lane": + label.Seg2d = ParseSeg2d(labelToLoad.Data) + } + item.Labels = append(item.Labels, label) + } + exportFile.Items = append(exportFile.Items, item) + } + } - exportJson, err := json.MarshalIndent(exportFile, "", " ") - if err != nil { - Error.Println(err) - } + exportJson, err := json.MarshalIndent(exportFile, "", " ") + if err != nil { + Error.Println(err) + } - //set relevant header. - w.Header().Set("Content-Disposition", "attachment; filename=" + projectName + "_Results.json") - io.Copy(w, bytes.NewReader(exportJson)) + //set relevant header. + w.Header().Set("Content-Disposition", "attachment; filename="+projectName+"_Results.json") + io.Copy(w, bytes.NewReader(exportJson)) } // Handles the download of submitted assignments func downloadTaskURLHandler(w http.ResponseWriter, r *http.Request) { var projectName = r.FormValue("project_name") - tasks := GetTasksInProject(projectName) + tasks, err := GetTasksInProject(projectName) + if err != nil { + Error.Println(err) + return + } taskURLs := []TaskURL{} for _, task := range tasks { @@ -545,23 +575,22 @@ func downloadTaskURLHandler(w http.ResponseWriter, r *http.Request) { } else { u.Scheme = "http" } - u.Host = r.Host - taskURL.URL = u.String() - taskURLs = append(taskURLs, taskURL) - } + u.Host = r.Host + taskURL.URL = u.String() + taskURLs = append(taskURLs, taskURL) + } - // downloadJson, err := json.MarshalIndent(taskURLs, "", " ") - downloadJson, err := JSONMarshal(taskURLs) - if err != nil { - Error.Println(err) - } + // downloadJson, err := json.MarshalIndent(taskURLs, "", " ") + downloadJson, err := JSONMarshal(taskURLs) + if err != nil { + Error.Println(err) + } - //set relevant header. - w.Header().Set("Content-Disposition", "attachment; filename=" + projectName + "_TaskURLs.json") - io.Copy(w, bytes.NewReader(downloadJson)) + //set relevant header. + w.Header().Set("Content-Disposition", "attachment; filename="+projectName+"_TaskURLs.json") + io.Copy(w, bytes.NewReader(downloadJson)) } -// DEPRECATED // handles item YAML file func getItemsFromProjectForm(w http.ResponseWriter, r *http.Request) []Item { var items []Item @@ -594,7 +623,6 @@ func getItemsFromProjectForm(w http.ResponseWriter, r *http.Request) []Item { return items } -// DEPRECATED // handles category YAML file, sets to default values if file missing func getCategoriesFromProjectForm(r *http.Request) []Category { labelType := r.FormValue("label_type") @@ -635,7 +663,6 @@ func getCategoriesFromProjectForm(r *http.Request) []Category { return categories } -// DEPRECATED // handles category YAML file, sets to default values if file missing func getAttributesFromProjectForm(r *http.Request) []Attribute { labelType := r.FormValue("label_type") @@ -673,7 +700,6 @@ func getAttributesFromProjectForm(r *http.Request) []Attribute { return attributes } -// DEPRECATED func CreateTasks(project Project) { index := 0 if project.Options.ItemType == "video" { @@ -688,11 +714,11 @@ func CreateTasks(project Project) { } else { // otherwise, make as many tasks as required size := len(project.Items) - for i := 0; i < size; i+= project.Options.TaskSize { + for i := 0; i < size; i += project.Options.TaskSize { task := Task{ ProjectOptions: project.Options, - Index: index, - Items: project.Items[i:Min(i+project.Options.TaskSize, size)], + Index: index, + Items: project.Items[i:Min(i+project.Options.TaskSize, size)], } index = index + 1 task.Save() @@ -702,25 +728,26 @@ func CreateTasks(project Project) { } // server side create form validation -func formValidation(w http.ResponseWriter, r *http.Request) bool { +func formValidation(w http.ResponseWriter, r *http.Request) error { if r.FormValue("project_name") == "" { w.Write([]byte("Please create a project name.")) - return false + return errors.New("Invalid form: no project name.") } if r.FormValue("item_type") == "" { w.Write([]byte("Please choose an item type.")) - return false + return errors.New("Invalid form: no item type.") } if r.FormValue("label_type") == "" { w.Write([]byte("Please choose a label type.")) - return false + return errors.New("Invalid form: no label type.") } if r.FormValue("task_size") == "" { w.Write([]byte("Please specify a task size.")) - return false + return errors.New("Invalid form: no task size.") } - return true + // TODO: check forms are actually uploaded + return nil } diff --git a/server/go/sat_test.go b/server/go/sat_test.go new file mode 100644 index 000000000..42c6fe303 --- /dev/null +++ b/server/go/sat_test.go @@ -0,0 +1,203 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "mime/multipart" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" +) + +const TEST_PROJECT_NAME = "Test_Project" +const TEST_PAGE_TITLE = "TEST PAGE TITLE" + +func init() { + Init(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr) + env = *NewEnv() +} + +func addFileToForm(writer *multipart.Writer, filePath string, fileField string) error { + file, err := os.Open(filePath) + if err != nil { + return err + } + fileContents, err := ioutil.ReadAll(file) + if err != nil { + return err + } + fi, err := file.Stat() + if err != nil { + return err + } + file.Close() + part, err := writer.CreateFormFile(fileField, fi.Name()) + if err != nil { + return err + } + _, err = part.Write(fileContents) + if err != nil { + return err + } + return nil +} + +// test project creation +func TestPostProject(t *testing.T) { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + project, err := GetProject(TEST_PROJECT_NAME) + if project.Options.Name != "" { + t.Fatal(TEST_PROJECT_NAME + " already exists.") + } + writer.WriteField("project_name", TEST_PROJECT_NAME) + writer.WriteField("item_type", "image") + writer.WriteField("label_type", "box2d") + writer.WriteField("page_title", TEST_PAGE_TITLE) + err = addFileToForm(writer, path.Join(env.SrcPath, "examples", "image_list.yml"), "item_file") + if err != nil { + t.Fatal(err) + } + err = addFileToForm(writer, path.Join(env.SrcPath, "examples", "categories.yml"), "categories") + if err != nil { + t.Fatal(err) + } + err = addFileToForm(writer, path.Join(env.SrcPath, "examples", "bbox_attributes.yml"), "attributes") + if err != nil { + t.Fatal(err) + } + writer.WriteField("task_size", "10") + writer.WriteField("vendor_id", "-1") + err = writer.Close() + if err != nil { + t.Fatal(err) + } + req, err := http.NewRequest("POST", "/postProject", body) + if err != nil { + t.Fatal(err) + } + req.Header.Add("Content-Type", writer.FormDataContentType()) + rr := httptest.NewRecorder() + postProjectHandler(rr, req) + project, err = GetProject(TEST_PROJECT_NAME) + if err != nil { + t.Fatal(err) + } + if project.Options.Name != TEST_PROJECT_NAME { + t.Fatal(errors.New("Project name was not saved correctly.")) + } + tasks, err := GetTasksInProject(TEST_PROJECT_NAME) + if err != nil { + t.Fatal(err) + } + if len(tasks) != 10 { + t.Fatal(errors.New("Incorrect number of tasks in project.")) + } +} + +// test dashboard +func TestDashboard(t *testing.T) { + req, err := http.NewRequest("POST", "dashboard?project_name="+TEST_PROJECT_NAME, nil) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + dashboardHandler(rr, req) + if rr.Code != 200 { + t.Fatal(errors.New(fmt.Sprintf("Dashboard handler HTTP code: %d", rr.Code))) + } +} + +// test vendor dashboard +func TestVendorDashboard(t *testing.T) { + req, err := http.NewRequest("POST", "vendor?project_name="+TEST_PROJECT_NAME, nil) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + vendorHandler(rr, req) + if rr.Code != 200 { + t.Fatal(errors.New(fmt.Sprintf("Vendor handler HTTP code: %d", rr.Code))) + } +} + +func TestLoadAssignment(t *testing.T) { + for i := 0; i < 10; i += 1 { + req, err := http.NewRequest("POST", "postLoadAssignment", + bytes.NewBuffer([]byte(fmt.Sprintf(`{"task": {"projectOptions": {"name": "%s"}, "index": "%d"}}`, TEST_PROJECT_NAME, i)))) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + postLoadAssignmentHandler(rr, req) + if rr.Code != 200 { + t.Fatal(errors.New(fmt.Sprintf("Load assignment handler HTTP code: %d", rr.Code))) + } + } +} + +func TestSaveHandler(t *testing.T) { + for i := 0; i < 10; i += 1 { + req, err := http.NewRequest("POST", "postSave", + bytes.NewBuffer([]byte(fmt.Sprintf(`{"task": {"projectOptions": {"name": "%s"}, "index":%d}, "labels": [{"id": 0, "categoryPath": "test"}, {"id": 1, "categoryPath": "test"}]}`, TEST_PROJECT_NAME, i)))) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + postSaveHandler(rr, req) + if rr.Code != 200 { + t.Fatal(errors.New(fmt.Sprintf("Save assignment handler HTTP code: %d", rr.Code))) + } + } +} + +func TestExportHandler(t *testing.T) { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("project_name", TEST_PROJECT_NAME) + err := writer.Close() + if err != nil { + t.Fatal(err) + } + req, err := http.NewRequest("POST", "/postProject", body) + if err != nil { + t.Fatal(err) + } + req.Header.Add("Content-Type", writer.FormDataContentType()) + rr := httptest.NewRecorder() + postExportHandler(rr, req) + if rr.Code != 200 { + t.Fatal(errors.New(fmt.Sprintf("Export handler HTTP code: %d", rr.Code))) + } +} + +func TestDownloadTaskURLHandler(t *testing.T) { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("project_name", TEST_PROJECT_NAME) + err := writer.Close() + if err != nil { + t.Fatal(err) + } + req, err := http.NewRequest("POST", "postDownloadTaskURL", body) + if err != nil { + t.Fatal(err) + } + req.Header.Add("Content-Type", writer.FormDataContentType()) + rr := httptest.NewRecorder() + downloadTaskURLHandler(rr, req) + if rr.Code != 200 { + t.Fatal(errors.New(fmt.Sprintf("Download task URL handler HTTP code: %d", rr.Code))) + } +} + +func TestDeleteProject(t *testing.T) { + err := DeleteProject(TEST_PROJECT_NAME) + if err != nil { + t.Fatal(err) + } +} diff --git a/server/go/utils.go b/server/go/utils.go index 420fb1a20..43aa43654 100644 --- a/server/go/utils.go +++ b/server/go/utils.go @@ -2,88 +2,71 @@ package main import ( "encoding/json" + "errors" "io/ioutil" "os" "path" + "sort" "strconv" + "strings" "time" "unicode/utf8" - "strings" ) // TODO: use actual worker ID const DEFAULT_WORKER = "default_worker" -func GetProject(projectName string) Project { +func GetProject(projectName string) (Project, error) { err := os.MkdirAll(env.DataDir, 0777) if err != nil { - Error.Println(err) + return Project{}, err } projectFilePath := path.Join(env.DataDir, projectName, "project.json") projectFileContents, err := ioutil.ReadFile(projectFilePath) if err != nil { - Error.Println(err) + return Project{}, err } project := Project{} err = json.Unmarshal(projectFileContents, &project) if err != nil { - Error.Println(err) + return Project{}, err } - return project + return project, nil } -// DEPRECATED -func GetProjects() []Project { - projectsDirectoryPath := path.Join(env.DataDir, "projects") - err := os.MkdirAll(projectsDirectoryPath, 0777) - if err != nil { - Error.Println(err) - } - projectsDirectoryContents, err := ioutil.ReadDir( - projectsDirectoryPath) +func DeleteProject(projectName string) error { + err := os.MkdirAll(env.DataDir, 0777) if err != nil { - Error.Println(err) - } - projects := []Project{} - for _, projectFile := range projectsDirectoryContents { - if len(projectFile.Name()) > 5 && - path.Ext(projectFile.Name()) == ".json" { - projectFileContents, err := ioutil.ReadFile( - path.Join(projectsDirectoryPath, projectFile.Name())) - if err != nil { - Error.Println(err) - } - project := Project{} - err = json.Unmarshal(projectFileContents, &project) - if err != nil { - Error.Println(err) - } - projects = append(projects, project) - } + return err } - return projects + projectFileDir := path.Join(env.DataDir, projectName) + os.RemoveAll(projectFileDir) + return nil } -func GetTask(projectName string, index string) Task { +func GetTask(projectName string, index string) (Task, error) { taskPath := path.Join(env.DataDir, projectName, "tasks", index+".json") taskFileContents, err := ioutil.ReadFile(taskPath) if err != nil { - Error.Println(err) + return Task{}, err } task := Task{} err = json.Unmarshal(taskFileContents, &task) if err != nil { - Error.Println(err) + return Task{}, err } - return task + return task, nil } -func GetTasksInProject(projectName string) []Task { +func GetTasksInProject(projectName string) ([]Task, error) { + if projectName == "" { + return []Task{}, errors.New("Empty project name") + } projectTasksPath := path.Join(env.DataDir, projectName, "tasks") os.MkdirAll(projectTasksPath, 0777) tasksDirectoryContents, err := ioutil.ReadDir(projectTasksPath) if err != nil { - Error.Println(err) + return []Task{}, err } tasks := []Task{} for _, taskFile := range tasksDirectoryContents { @@ -92,28 +75,32 @@ func GetTasksInProject(projectName string) []Task { taskFileContents, err := ioutil.ReadFile( path.Join(projectTasksPath, taskFile.Name())) if err != nil { - Error.Println(err) + return []Task{}, err } task := Task{} err = json.Unmarshal(taskFileContents, &task) if err != nil { - Error.Println(err) + return []Task{}, err } tasks = append(tasks, task) } } - return tasks + // sort tasks by index + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Index < tasks[j].Index + }) + return tasks, nil } // Get the most recent assignment given the needed fields. -func GetAssignment(projectName string, taskIndex string, workerId string) Assignment { +func GetAssignment(projectName string, taskIndex string, workerId string) (Assignment, error) { assignment := Assignment{} submissionsPath := path.Join(env.DataDir, projectName, "submissions", taskIndex, workerId) os.MkdirAll(submissionsPath, 0777) submissionsDirectoryContents, err := ioutil.ReadDir(submissionsPath) if err != nil { - Error.Println(err) + return Assignment{}, err } // directory contents should already be sorted, just need to remove all non-JSON submissionsDirectoryJSONs := []os.FileInfo{} @@ -123,47 +110,58 @@ func GetAssignment(projectName string, taskIndex string, workerId string) Assign } } // if any submissions exist, get the most recent one - if (len(submissionsDirectoryJSONs) > 0) { + if len(submissionsDirectoryJSONs) > 0 { submissionFileContents, err := ioutil.ReadFile(path.Join(submissionsPath, submissionsDirectoryJSONs[len(submissionsDirectoryJSONs)-1].Name())) if err != nil { - Error.Println(err) + return Assignment{}, err } err = json.Unmarshal(submissionFileContents, &assignment) if err != nil { - Error.Println(err) + return Assignment{}, err } } else { assignmentPath := path.Join(env.DataDir, projectName, "assignments", taskIndex, workerId+".json") assignmentFileContents, err := ioutil.ReadFile(assignmentPath) if err != nil { - Error.Println(err) + return Assignment{}, err } err = json.Unmarshal(assignmentFileContents, &assignment) if err != nil { - Error.Println(err) + return Assignment{}, err } } - return assignment + return assignment, nil } -func CreateAssignment(projectName string, taskIndex string, workerId string) Assignment { - task := GetTask(projectName, taskIndex) +func CreateAssignment(projectName string, taskIndex string, workerId string) (Assignment, error) { + task, err := GetTask(projectName, taskIndex) + if err != nil { + return Assignment{}, err + } assignment := Assignment{ - Task: task, - WorkerId: workerId, + Task: task, + WorkerId: workerId, StartTime: recordTimestamp(), } assignment.Initialize() - return assignment + return assignment, nil } -func GetDashboardContents(projectName string) DashboardContents { - return DashboardContents{ - Project: GetProject(projectName), - Tasks: GetTasksInProject(projectName), +func GetDashboardContents(projectName string) (DashboardContents, error) { + project, err := GetProject(projectName) + if err != nil { + return DashboardContents{}, err + } + tasks, err := GetTasksInProject(projectName) + if err != nil { + return DashboardContents{}, err } + return DashboardContents{ + Project: project, + Tasks: tasks, + }, nil } func GetHandlerUrl(itemType string, labelType string) string { @@ -187,12 +185,12 @@ func GetHandlerUrl(itemType string, labelType string) string { return "NO_VALID_HANDLER" } case "pointcloud": - switch labelType { - case "box3d": - return "point_cloud_labeling" - default: - return "NO_VALID_HANDLER" - } + switch labelType { + case "box3d": + return "point_cloud_labeling" + default: + return "NO_VALID_HANDLER" + } } return "NO_VALID_HANDLER" } @@ -263,7 +261,7 @@ func CheckProjectName(projectName string) string { } // default box2d category if category file is missing -var defaultBox2dCategories = []Category { +var defaultBox2dCategories = []Category{ {"person", nil}, {"rider", nil}, {"car", nil}, @@ -277,21 +275,21 @@ var defaultBox2dCategories = []Category { } // default seg2d category if category file is missing -var defaultSeg2dCategories = []Category { - {"void", []Category { +var defaultSeg2dCategories = []Category{ + {"void", []Category{ {"unlabeled", nil}, {"dynamic", nil}, {"ego vehicle", nil}, {"ground", nil}, {"static", nil}, }}, - {"flat", []Category { + {"flat", []Category{ {"parking", nil}, {"rail track", nil}, {"road", nil}, {"sidewalk", nil}, }}, - {"construction", []Category { + {"construction", []Category{ {"bridge", nil}, {"building", nil}, {"bus stop", nil}, @@ -301,7 +299,7 @@ var defaultSeg2dCategories = []Category { {"tunnel", nil}, {"wall", nil}, }}, - {"object", []Category { + {"object", []Category{ {"banner", nil}, {"billboard", nil}, {"fire hydrant", nil}, @@ -318,18 +316,18 @@ var defaultSeg2dCategories = []Category { {"traffic sign frame", nil}, {"trash can", nil}, }}, - {"nature", []Category { + {"nature", []Category{ {"terrain", nil}, {"vegetation", nil}, }}, - {"sky", []Category { + {"sky", []Category{ {"sky", nil}, }}, - {"human", []Category { + {"human", []Category{ {"person", nil}, {"rider", nil}, }}, - {"vehicle", []Category { + {"vehicle", []Category{ {"bicycle", nil}, {"bus", nil}, {"car", nil}, @@ -342,7 +340,7 @@ var defaultSeg2dCategories = []Category { } // default lane2d category if category file is missing -var defaultLane2dCategories = []Category { +var defaultLane2dCategories = []Category{ {"road curb", nil}, {"double white", nil}, {"double yellow", nil}, @@ -354,7 +352,7 @@ var defaultLane2dCategories = []Category { } // default box2d attributes if attribute file is missing -var defaultBox2dAttributes = []Attribute { +var defaultBox2dAttributes = []Attribute{ {"Occluded", "switch", "o", "", nil, nil, nil, }, @@ -369,9 +367,8 @@ var defaultBox2dAttributes = []Attribute { // default attributes if attribute file is missing // to avoid uncaught type error in Javascript file -var dummyAttribute = []Attribute { +var dummyAttribute = []Attribute{ {"", "", "", "", nil, nil, nil, }, } -