def test_relation(): s1 = shape.SHAPE_IMPLS['square'](color_='red', x=30, y=30) # S2 is below - higher coordinates s2 = shape.SHAPE_IMPLS['square'](color_='blue', x=40, y=40) assert s1.left(s2) assert not s1.left(s1) assert not s1.right(s2) assert not s2.right(s2) assert s1.above(s2) assert s2.below(s1) assert not s1.below(s2) assert not s2.above(s1) # left cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 0, 0) assert config.has_relation(s1, s2, cfg.relation, cfg.dir) assert not config.has_relation(s2, s1, cfg.relation, cfg.dir) # right cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 0, 1) assert config.has_relation(s2, s1, cfg.relation, cfg.dir) assert not config.has_relation(s1, s2, cfg.relation, cfg.dir) # below cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 1, 0) assert config.has_relation(s2, s1, cfg.relation, cfg.dir) assert not config.has_relation(s1, s2, cfg.relation, cfg.dir) # above cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 1, 1) assert config.has_relation(s1, s2, cfg.relation, cfg.dir) assert not config.has_relation(s2, s1, cfg.relation, cfg.dir)
def test_relation_lang(): cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 0, 0) assert lang.fmt_config(cfg) == 'red square left blue square' cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 0, 1) assert lang.fmt_config(cfg) == 'red square right blue square' cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 1, 0) assert lang.fmt_config(cfg) == 'red square below blue square' cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 1, 1) assert lang.fmt_config(cfg) == 'red square above blue square'
def test_matches_shapes(): cfg = config.SpatialConfig((('red', None), ('blue', 'square')), 0, 0) s1 = shape.SHAPE_IMPLS['square'](color_='red') assert cfg.matches_shapes(s1) == [0] s2 = shape.SHAPE_IMPLS['square'](color_='blue') assert cfg.matches_shapes(s2) == [1] cfg = config.SpatialConfig((('red', None), (None, 'square')), 0, 0) s1 = shape.SHAPE_IMPLS['square'](color_='red') assert cfg.matches_shapes(s1) == [0, 1] s2 = shape.SHAPE_IMPLS['square'](color_='blue') assert cfg.matches_shapes(s2) == [1] s2 = shape.SHAPE_IMPLS['triangle'](color_='blue') assert cfg.matches_shapes(s2) == []
def random_config_spatial(self): # 0 -> only shape specified # 1 -> only color specified # 2 -> only both specified shape_1_spec = config.ShapeSpec(random.randint(3)) shape_2_spec = config.ShapeSpec(random.randint(3)) shape_1 = self.random_shape_from_spec(shape_1_spec) shape_2 = self.random_shape_from_spec(shape_2_spec) if shape_1 == shape_2: return self.random_config_spatial() relation = random.randint(2) relation_dir = random.randint(2) return config.SpatialConfig((shape_1, shape_2), relation, relation_dir)
def test_does_not_validate_1(): cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 0, 0) s1 = shape.SHAPE_IMPLS['square'](color_='red', x=30, y=30) s2 = shape.SHAPE_IMPLS['square'](color_='blue', x=40, y=30) s3 = shape.SHAPE_IMPLS['triangle'](color_='blue') assert not cfg.does_not_validate([s1], s2) assert not cfg.does_not_validate([s2], s1) assert not cfg.does_not_validate([s1, s2], s1) assert not cfg.does_not_validate([s1, s2], s2) assert cfg.does_not_validate([s1, s2], s3) assert not cfg.does_not_validate([s1, s3], s2) assert not cfg.does_not_validate([s2, s3, s1], s1)
def test_matching(): cfg = config.SpatialConfig((('red', 'square'), ('blue', 'square')), 0, 0) s1 = shape.SHAPE_IMPLS['square'](color_='red', x=30, y=30) s2 = shape.SHAPE_IMPLS['square'](color_='blue', x=40, y=30) assert config.matches(cfg.shapes[0], s1) assert config.matches(cfg.shapes[1], s2) assert not config.matches(cfg.shapes[1], s1) assert not config.matches(cfg.shapes[0], s2) assert config.matches((None, None), s1) assert config.matches((None, None), s2) assert config.matches(('red', None), s1) assert not config.matches(('red', None), s2) assert config.matches((None, 'square'), s1) assert config.matches((None, 'square'), s2)
def invalidate_spatial(self, cfg): # Invalidate by randomly choosing one property to change: ((shape_1_color, shape_1_shape), (shape_2_color, shape_2_shape)), relation, relation_dir = cfg properties = [] if shape_1_color is not None: properties.append(config.ConfigProps.SHAPE_1_COLOR) if shape_1_shape is not None: properties.append(config.ConfigProps.SHAPE_1_SHAPE) if shape_2_color is not None: properties.append(config.ConfigProps.SHAPE_2_COLOR) if shape_2_shape is not None: properties.append(config.ConfigProps.SHAPE_2_SHAPE) sp = len(properties) # Invalidate relations half of the time for _ in range(sp): properties.append(config.ConfigProps.RELATION_DIR) # Randomly select property to invalidate # TODO: Support for invalidating multiple properties invalid_prop = random.choice(properties) if invalid_prop == config.ConfigProps.SHAPE_1_COLOR: inv_cfg = ((self.new_color(shape_1_color), shape_1_shape), (shape_2_color, shape_2_shape)), relation, relation_dir elif invalid_prop == config.ConfigProps.SHAPE_1_SHAPE: inv_cfg = ((shape_1_color, self.new_shape(shape_1_shape)), (shape_2_color, shape_2_shape)), relation, relation_dir elif invalid_prop == config.ConfigProps.SHAPE_2_COLOR: inv_cfg = ((shape_1_color, shape_1_shape), (self.new_color(shape_2_color), shape_2_shape)), relation, relation_dir elif invalid_prop == config.ConfigProps.SHAPE_2_SHAPE: inv_cfg = ((shape_1_color, shape_1_shape), (shape_2_color, self.new_shape(shape_2_shape))), relation, relation_dir elif invalid_prop == config.ConfigProps.RELATION_DIR: inv_cfg = ((shape_1_color, shape_1_shape), (shape_2_color, shape_2_shape)), relation, 1 - relation_dir else: raise RuntimeError return config.SpatialConfig(*inv_cfg)
def test_does_not_validate_3(): # i.e. above cfg = config.SpatialConfig((('green', None), (None, 'triangle')), 1, 1) s1 = shape.SHAPE_IMPLS['ellipse'](color_='green', x=0, y=20) s2 = shape.SHAPE_IMPLS['triangle'](color_='white', x=0, y=30) assert not cfg.does_not_validate([s2], s1) assert not cfg.does_not_validate([s1], s2) assert not cfg.does_not_validate([s1, s2], s1) assert not cfg.does_not_validate([s1, s2], s2) s3 = shape.SHAPE_IMPLS['triangle'](color_='red', x=0, y=31) assert not cfg.does_not_validate([s1, s2], s3) s4 = shape.SHAPE_IMPLS['triangle'](color_='red', x=0, y=18) assert cfg.does_not_validate([s1, s2], s4) s5 = shape.SHAPE_IMPLS['square'](color_='green', x=0, y=25) assert not cfg.does_not_validate([s1, s2], s5) s6 = shape.SHAPE_IMPLS['circle'](color_='green', x=0, y=35) assert cfg.does_not_validate([s1, s2], s6)
def test_does_not_validate_2(): cfg = config.SpatialConfig((('red', None), ('blue', None)), 0, 0) s1 = shape.SHAPE_IMPLS['square'](color_='red') s1.x = 30 s1.y = 30 s2 = shape.SHAPE_IMPLS['square'](color_='blue') s2.x = 40 s2.y = 30 assert not cfg.does_not_validate([s1], s2) assert not cfg.does_not_validate([s1], s2) assert not cfg.does_not_validate([s2], s1) s3 = shape.SHAPE_IMPLS['triangle'](color_='red', x=10, y=10) assert not cfg.does_not_validate([s1, s2], s3) s4 = shape.SHAPE_IMPLS['triangle'](color_='red', x=39, y=10) assert not cfg.does_not_validate([s1, s2], s4) s5 = shape.SHAPE_IMPLS['triangle'](color_='red', x=41, y=10) assert cfg.does_not_validate([s1, s2], s5)
def generate_spatial(self, cfg, label, n_caps_per_image): """ Generate a single spatial relation according to the config, invalidating it if the label is 0. """ cfg_attempts = 0 while cfg_attempts < c.MAX_INVALIDATE_ATTEMPTS: new_cfg = cfg if label else self.invalidate_spatial(cfg) (ss1, ss2), relation, relation_dir = new_cfg s2 = self.add_shape_from_spec(ss2, relation, relation_dir) # Place second shape attempts = 0 while attempts < c.MAX_PLACEMENT_ATTEMPTS: s1 = self.add_shape_rel(ss1, s2, relation, relation_dir) if not s2.intersects(s1): break attempts += 1 else: raise RuntimeError( "Could not place shape onto image without intersection") if label: # Positive example break elif cfg.does_not_validate([s1], s2): break else: # print(f"Shapes {[s1, s2]} for invalid config '{str(new_cfg)}' validates the original config '{str(cfg)}' (attempt {cfg_attempts})") assert True cfg_attempts += 1 # Place distractor shapes shapes = [s1, s2] n_dist = self.sample_n_distractor() for _ in range(n_dist): attempts = 0 while attempts < c.MAX_DISTRACTOR_PLACEMENT_ATTEMPTS: dss = self.sample_distractor(existing_shapes=shapes) ds = self.add_shape(dss) # No intersections if not any(ds.intersects(s) for s in shapes): if label: shapes.append(ds) break else: # If this is a *negative* example, we should not have # the relation expressed by the original config if cfg.does_not_validate(shapes, ds): shapes.append(ds) break else: # print(f"Adding new shape {ds} to existing shapes {shapes} validates the original config {str(cfg)} (attempt {attempts})") assert True attempts += 1 else: raise RuntimeError("Could not place distractor onto " "image without intersection") distractor_configs_to_cap = [] non_target_pairs = [(i, j) for i in range(len(shapes)) for j in range(len(shapes)) if i != j] cfgs_to_cap = np.random.permutation(np.arange(len(non_target_pairs))) rel_to_cap_idx = np.random.randint(0, 2, (len(non_target_pairs), )) successes = 0 i = 0 flipped = False while successes < n_caps_per_image: if i == len(non_target_pairs): if not flipped: flipped = True i = 0 rel_to_cap_idx = 1 - rel_to_cap_idx # change rel to cap and retry else: raise RuntimeError( 'Not distinct pairs to make captions about') shape_1_to_cap = shapes[non_target_pairs[cfgs_to_cap[i]][0]] shape_2_to_cap = shapes[non_target_pairs[cfgs_to_cap[i]][1]] rel_to_cap = rel_to_cap_idx[i] i += 1 if rel_to_cap == 0: if shape_1_to_cap.x >= shape_2_to_cap.x + c.BUFFER: rel_dir_to_cap = 1 elif shape_1_to_cap.x <= shape_2_to_cap.x - c.BUFFER: rel_dir_to_cap = 0 else: continue else: if shape_1_to_cap.y <= shape_2_to_cap.y - c.BUFFER: rel_dir_to_cap = 1 elif shape_1_to_cap.y >= shape_2_to_cap.y + c.BUFFER: rel_dir_to_cap = 0 else: continue spec_1 = np.random.randint(0, 3) if spec_1 == 0: shape_1_to_cap = (shape_1_to_cap.color, shape_1_to_cap.name) elif spec_1 == 1: shape_1_to_cap = (shape_1_to_cap.color, None) else: shape_1_to_cap = (None, shape_1_to_cap.name) spec_2 = np.random.randint(0, 3) if spec_2 == 0: shape_2_to_cap = (shape_2_to_cap.color, shape_2_to_cap.name) elif spec_2 == 1: shape_2_to_cap = (shape_2_to_cap.color, None) else: shape_2_to_cap = (None, shape_2_to_cap.name) distractor_configs_to_cap.append( config.SpatialConfig((shape_1_to_cap, shape_2_to_cap), rel_to_cap, rel_dir_to_cap)) successes += 1 return new_cfg, shapes, distractor_configs_to_cap