def test_shape_similarity(self): """ Verify that the shape similarity measure is producing sane outputs. """ # make a dummy scene scene = ProjectScene("bounding_box") evaluator = BBEvaluator(scene, scene, self.settings) obj1 = next(iter(self.submission.elements.values())) # ::: Temp only, use copy of obj1 if deep copy can be made to work obj2 = next(iter(self.ground_truth.elements.values())) # verify no offset gives sim = 1 sim = evaluator._shape_similarity(obj1, obj2) self.assertAlmostEqual(sim, 1) # verify small offset gives sim between 0 and 1 pose_orig = obj2.pose obj2.pose = Pose3(t=pose_orig.t + [0.1, 0, 0], R=pose_orig.R) sim = evaluator._shape_similarity(obj1, obj2) self.assertTrue(sim < 1 and sim > 0) # verify large offset gives sim = 0 obj2.pose = Pose3(t=pose_orig.t + [5, 5, 5], R=pose_orig.R) sim = evaluator._shape_similarity(obj1, obj2) self.assertAlmostEqual(sim, 0)
def test_compose(self): expected = Pose3(self.pose.rotation(), Vector3(1, 5, 1)) translate = Pose3(t=Vector3(0, 0, 4)) # translate Z axis, which is Y actual = self.pose.compose(translate) actual.assert_equal(expected) actual2 = self.pose * translate actual2.assert_equal(expected)
def example(cls): """Create a simple ProjectObjectDict.""" pose1 = Pose3() pose2 = Pose3(t=Vector3(2, 2, 0)) pose3 = Pose3(t=Vector3(4, 2, 0)) # Be explicit about unicode literal in case this code is called from python 2 data_path = parutil.get_dir_path(u"sumo/threedee/test_data") model1 = GltfModel.load_from_glb(os.path.join(data_path, "bed.glb")) model2 = GltfModel.load_from_glb(os.path.join(data_path, "bed.glb")) model3 = GltfModel.load_from_glb(os.path.join(data_path, "bed.glb")) obj1 = ProjectObject.gen_meshes_object( meshes=model1, id="1", pose=pose1, category="bed" ) obj2 = ProjectObject.gen_meshes_object( meshes=model2, id="2", pose=pose2, category="chair" ) obj3 = ProjectObject.gen_meshes_object( meshes=model3, id="3", pose=pose3, category="bed" ) project_object_dict = ProjectObjectDict() project_object_dict[obj1.id] = obj1 project_object_dict[obj2.id] = obj2 project_object_dict[obj3.id] = obj3 return project_object_dict
def test_pose_error(self): """ Test rotation and translation error metric. """ self.ground_truth = ProjectScene.load(self.data_path, 'bounding_box_sample2') self.submission = ProjectScene.load(self.data_path, 'bounding_box_sample2') self.settings.thresholds = [0.5] # verify that correct pose is ok evaluator = BBEvaluator(self.submission, self.ground_truth, self.settings) rotation_error, translation_error = evaluator.pose_error() self.assertAlmostEqual(rotation_error, 0) self.assertAlmostEqual(translation_error, 0) # verify that rotation by symmetry amount is ok pose_orig = self.submission.elements["1069"].pose new_pose = Pose3(R=pose_orig.R * Rot3.Ry(math.pi), t=pose_orig.t) self.submission.elements["1069"].pose = new_pose evaluator = BBEvaluator(self.submission, self.ground_truth, self.settings) rotation_error, translation_error = evaluator.pose_error() self.assertAlmostEqual(rotation_error, 0) self.assertAlmostEqual(translation_error, 0) # verify that rotation by non-symmetry amount give correct error new_pose = Pose3(R=pose_orig.R * Rot3.Ry(math.radians(10)), t=pose_orig.t) self.submission.elements["1069"].pose = new_pose evaluator = BBEvaluator(self.submission, self.ground_truth, self.settings) rotation_error, translation_error = evaluator.pose_error() self.assertAlmostEqual(rotation_error, math.radians(10)) self.assertAlmostEqual(translation_error, 0) # verify that translation gives translation error new_pose = Pose3(R=pose_orig.R, t=pose_orig.t + [0.05, 0, 0]) self.submission.elements["1069"].pose = new_pose evaluator = BBEvaluator(self.submission, self.ground_truth, self.settings) rotation_error, translation_error = evaluator.pose_error() self.assertAlmostEqual(rotation_error, 0) self.assertAlmostEqual(translation_error, 0.05) # verify that empty sumission gives None, None new_pose = Pose3(R=pose_orig.R, t=pose_orig.t + [1, 0, 0]) self.submission.elements["1069"].pose = new_pose evaluator = BBEvaluator(self.submission, self.ground_truth, self.settings) rotation_error, translation_error = evaluator.pose_error() self.assertEqual(rotation_error, None) self.assertEqual(translation_error, None)
def test_register(self): ''' Test registering point clouds in different frames. ''' t = Vector3(1, 1, 1) R = Rot3() T = Pose3(R, t).inverse() cloud = PointCloud(np.zeros((3, 1))) registred_cloud = PointCloud.register([(Pose3(), cloud), (T, cloud)]) np.testing.assert_array_equal( registred_cloud.points(), np.column_stack([Vector3(0, 0, 0), Vector3(-1.0, -1.0, -1.0)]))
def test_xml(self): """ Test conversion to and from xml. This is just a basic functionality test of a round trip from Pose3 to xml and back. """ pose = Pose3(t=Vector3(1.5, 2.6, 3.7), R=np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])) pose_xml = pose.to_xml() self.assertEqual(pose_xml.tag, 'pose') pose_rt = Pose3.from_xml(pose_xml) pose.assert_almost_equal(pose_rt)
def test_surreal(self): """Read from a Surreal json file.""" # Expected pose. expected_pose = Pose3(t=Vector3(1, 2, 3)) # Read json file path_to_json_linear = os.path.join(PATH, 'pose3_test.json') data = json.load(open(path_to_json_linear)) pose = Pose3.from_surreal(data['T_cr']) # Test pose pose.assert_almost_equal(expected_pose)
def __init__(self, id, project_type="bounding_box", bounds=None, voxels=None, meshes=None, pose=None, category="unknown", symmetry=None, score=-1, evaluated=True): """ Constructor. Preferred method of creation is one of the factory methods: gen_bounding_box_object, gen_voxels_object, or gen_meshes_object. For this constructor, representation is selected based on the <project_type>: bounding box track: bounds is used voxels track: voxels and bounds are used mesh track: meshes and bounds are used Inputs: id (string) - Unique identifier for the object project_type (string) - Specifies the project type to construct (valid values are "bounding_box", "voxels", or "meshes") bounds (Box3d) - Object bounding box in local coordinates voxels (VoxelGrid) - Object voxel shape in local coordinates meshes (GltfModel) - Object mesh shape and appearance in local coordinates pose (Pose3) - Transforms object from local coordinates to world coordinates category (string) - Object category (e.g., chair, bookcase, etc.) symmetry (ObjectSymmetry) - Object symmetry description score (float) - Detection score evaluated (Boolean) - Indicates whether this object will be used in evaluation metric. Only relevant for ground truth scenes. Exceptions: ValueError - if project_type is not one of the allowed values. """ # ensure id is unicode string, idiom below is python2/3 compatible self._id = id.decode('UTF-8') if hasattr(id, 'decode') else id self._project_type = project_type self.pose = pose if pose is not None else Pose3() self.category = category self.symmetry = symmetry if symmetry is not None else ObjectSymmetry() self.score = score self.evaluated = evaluated if project_type == "bounding_box": self.bounds = bounds self.voxels = None self.meshes = None elif project_type == "voxels": self.bounds = bounds self.voxels = voxels self.meshes = None elif project_type == "meshes": self.bounds = bounds self.voxels = None self.meshes = meshes else: raise ValueError("Invalid project_type: " + project_type)
def test_transform_from(self): '''Test transform_from.''' t = Vector3(1, 1, 1) R = Rot3() T = Pose3(R, t).inverse() cloud = PointCloud(np.zeros((3, 1))) new_cloud = cloud.transform_from(T) np.testing.assert_array_equal(new_cloud.points(), [[-1.0], [-1.0], [-1.0]])
def test_semantic_score(self): # We cannot test Evaluator directly. This creates the similarity cache and # does data association evaluator = BBEvaluator(self.submission, self.ground_truth, self.settings) # submission and GT are exactly the same, should get mAP = 1 semantic_score = evaluator.semantics_score() expected = 1 self.assertEqual( semantic_score, expected, "Expected semantic score of %.3f, found %.3f.\n" % (expected, semantic_score)) # move the coffee table by a bit to get IoU ~ 0.72. Should # get 0.5 since the average precision in 5 of the 10 # thresholds is 1 and in the other cases it is 0. table = self.submission.elements["1069"] settings = self.settings settings.categories = ["coffee_table"] pose_orig = table.pose table.pose = Pose3(t=pose_orig.t + [0.1, 0, 0], R=pose_orig.R) evaluator2 = BBEvaluator(self.submission, self.ground_truth, settings) semantic_score = evaluator2.semantics_score() expected = 0.5 self.assertAlmostEqual( semantic_score, expected, 3, "Expected semantic score of %.3f, found %.3f.\n" % (expected, semantic_score)) # move the coffee table by a lot to get IoU < 0.5. table = self.submission.elements["1069"] settings = self.settings settings.categories = ["coffee_table"] table.pose = Pose3(t=pose_orig.t + [0.5, 0, 0], R=pose_orig.R) evaluator2 = BBEvaluator(self.submission, self.ground_truth, settings) semantic_score = evaluator2.semantics_score() expected = 0 self.assertAlmostEqual( semantic_score, expected, 3, "Expected semantic score of %.3f, found %.3f.\n" % (expected, semantic_score))
def example(cls, id="1"): """Create a simple ProjectObject of project_type = meshes.""" meshes = GltfModel.example() pose = Pose3(t=Vector3(1, 2, 3)) symmetry = ObjectSymmetry.example() return cls.gen_meshes_object(id=id, pose=pose, category="chair", meshes=meshes, symmetry=symmetry, score=0.57)
def _parse_xml(base_elem): """ Parse the xml of an <element> tag, extracting the ProjectObject attributes. Inputs: base_elem (ET.Element) - An Element with tag "element" and appropriate sub-elements. Return: tuple (id, pose, category, bounds, symmetry, score, evaluated) ProjectObject attributes (see constructor for details). Exceptions: ValueError - If base_elem is not <element> or if none of its children is <id>. """ # verify base_elem tag is 'element' if base_elem.tag != "element": raise ValueError('Expected tag to be "element"') # defaults proxy = ProjectObject(1) category = proxy.category pose = proxy.pose symmetry = proxy.symmetry score = proxy.score evaluated = proxy.evaluated for elem in base_elem: if elem.tag == "id": id = elem.text elif elem.tag == "pose": pose = Pose3.from_xml(elem) elif elem.tag == "category": category = elem.text elif (elem.tag == "bounds"): # Note: Boxe3d.from_xml expects tag to be 'box3d' elem_temp = deepcopy(elem) elem_temp.tag = "box3d" bounds = Box3d.from_xml(elem_temp) elif elem.tag == "symmetry": symmetry = ObjectSymmetry.from_xml(elem) elif elem.tag == "detectionScore": score = float(elem.text) elif elem.tag == "evaluated": evaluated = bool(elem.text) if id is None: raise ValueError("XML is missing required <id> tag.") return (id, pose, category, bounds, symmetry, score, evaluated)
def test_shape_similarity(self): """ Verify that the shape similarity measure is producing sane outputs. """ evaluator = BBEvaluator(self.submission, self.ground_truth, self.settings) obj1 = self.submission.elements["51"] obj2 = self.ground_truth.elements["51"] # verify no offset gives sim = 1 sim = evaluator._shape_similarity(obj1, obj2) self.assertAlmostEqual(sim, 1) # verify small offset gives sim between 0 and 1 pose_orig = obj2.pose obj2.pose = Pose3(t=pose_orig.t + [0.1, 0, 0], R=pose_orig.R) sim = evaluator._shape_similarity(obj1, obj2) self.assertTrue(sim < 1 and sim > 0) # verify large offset gives sim = 0 obj2.pose = Pose3(t=pose_orig.t + [5, 5, 5], R=pose_orig.R) sim = evaluator._shape_similarity(obj1, obj2) self.assertAlmostEqual(sim, 0)
def setUp(self): # create sample bounding box, meshes, and voxel grid objects self.bounds = Box3d([-1.5, -2.2, 3], [4, 4.1, 6.5]) textured_mesh = self.bounds.to_textured_mesh() self.meshes = GltfModel.from_textured_mesh(textured_mesh) points = np.array([[0.1, 0.1, 0.1], [1.1, 1.1, 1.1], [1.3, 1.2, 1.4]]) self.voxels = VoxelGrid(0.5, min_corner=Vector3f(0, 0, 0), points=points) # and pose rot = np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]], dtype=float) trans = Vector3f(1, 1, 1) self.pose = Pose3(rot, trans) # Create temporary outout directory. self.temp_directory = tempfile.mkdtemp()
def test_constructor(self): """ Test constructor. """ po = ProjectObject(id="1", project_type="bounding_box", bounds=self.bounds) self.assertTrue(isinstance(po.pose, Pose3)) self.assertTrue(isinstance(po.category, "".__class__)) po.pose.assert_almost_equal(Pose3()) self.assertTrue(po.bounds.almost_equal(self.bounds)) self.assertIs(po.meshes, None) self.assertIs(po.voxels, None) self.assertTrue(po.category == "unknown") self.assertEqual(po.id, "1") self.assertEqual(po.symmetry, ObjectSymmetry()) self.assertAlmostEqual(po.score, -1)
def test_surreal_coding(self): """Test conversion to/from surreal-style json dict.""" json_dict = self.pose.to_surreal() decoded_pose = Pose3.from_surreal(json_dict) decoded_pose.assert_almost_equal(self.pose)
def test_json(self): """Test conversion to/from json dict.""" json_dict = self.pose.to_json() decoded_pose = Pose3.from_json(json_dict) decoded_pose.assert_almost_equal(self.pose)
def test_inverse(self): expected = Pose3() actual1 = self.pose.inverse() * self.pose actual1.assert_equal(expected) actual2 = self.pose * self.pose.inverse() actual2.assert_equal(expected)
def setUp(self): # Camera with z-axis looking at world y-axis wRc = np.transpose( np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]], dtype=float)) t = Vector3(1, 1, 1) self.pose = Pose3(wRc, t)
def test_ENU_camera(self): Pose3.ENU_camera(position=Vector3(1, 1, 1)).assert_equal(self.pose)
def test_transform_pose(self): po = ProjectObject(id="1", bounds=self.bounds, pose=Pose3()) po2 = po.transform_pose(self.pose) self.assertTrue(po2.pose.almost_equal(self.pose))