def setUp(self): self.windows = [ Box.make_square(0, 0, 10), Box.make_square(0, 5, 10), Box.make_square(0, 10, 10) ] self.num_classes = 3 self.scores_left = make_random_scores(self.num_classes, 10, 10) self.scores_mid = make_random_scores(self.num_classes, 10, 10) self.scores_right = make_random_scores(self.num_classes, 10, 10) self.scores_left = self.scores_left.astype(np.float16) self.scores_mid = self.scores_mid.astype(np.float16) self.scores_right = self.scores_right.astype(np.float16) self.extent = Box(0, 0, 10, 20) self.labels = SemanticSegmentationSmoothLabels( extent=self.extent, num_classes=self.num_classes) self.labels[self.windows[0]] = self.scores_left self.labels[self.windows[1]] = self.scores_mid self.labels[self.windows[2]] = self.scores_right arr = np.zeros((self.num_classes, 10, 20), dtype=np.float16) arr[..., :10] += self.scores_left arr[..., 5:15] += self.scores_mid arr[..., 10:] += self.scores_right self.expected_scores = arr hits = np.zeros((10, 20), dtype=np.uint8) hits[..., :10] += 1 hits[..., 5:15] += 1 hits[..., 10:] += 1 self.expected_hits = hits
def test_get_labels_inferred(self): extent = Box.make_square(0, 0, 8) msg = rv.LabelSourceConfig.builder(rv.CHIP_CLASSIFICATION) \ .with_uri(self.uri) \ .with_ioa_thresh(0.5) \ .with_use_intersection_over_cell(False) \ .with_pick_min_class_id(False) \ .with_background_class_id(self.background_class_id) \ .with_infer_cells(True) \ .with_cell_size(4) \ .build().to_proto() config = rv.LabelSourceConfig.builder(rv.CHIP_CLASSIFICATION) \ .from_proto(msg).build() source = config.create_source(self.task_config, extent, self.crs_transformer, self.temp_dir.name) labels = source.get_labels() cells = labels.get_cells() self.assertEqual(len(cells), 4) self.assertEqual(labels.get_cell_class_id(self.box1), self.class_id1) self.assertEqual(labels.get_cell_class_id(self.box2), self.class_id2) self.assertEqual(labels.get_cell_class_id(Box.make_square(0, 4, 4)), self.background_class_id) self.assertEqual(labels.get_cell_class_id(Box.make_square(4, 0, 4)), self.background_class_id)
def test_read_with_extent(self): # Extent only includes the first box. extent = Box.make_square(0, 0, 3) store = ObjectDetectionLabelSource(self.file_path, self.crs_transformer, self.class_map, extent=extent) labels = store.get_labels() npboxes = np.array([[0., 0., 2., 2.]]) class_ids = np.array([1]) scores = np.array([0.9]) expected_labels = ObjectDetectionLabels(npboxes, class_ids, scores=scores) labels.assert_equal(expected_labels) # Extent includes both boxes, but clips the second. extent = Box.make_square(0, 0, 3.9) store = ObjectDetectionLabelSource(self.file_path, self.crs_transformer, self.class_map, extent=extent) labels = store.get_labels() npboxes = np.array([[0., 0., 2., 2.], [2., 2., 3.9, 3.9]]) class_ids = np.array([1, 2]) scores = np.array([0.9, 0.9]) expected_labels = ObjectDetectionLabels(npboxes, class_ids, scores=scores) labels.assert_equal(expected_labels)
def setUp(self): self.windows = [Box.make_square(0, 0, 10), Box.make_square(0, 10, 10)] self.label_arr0 = np.random.choice([0, 1], (10, 10)) self.label_arr1 = np.random.choice([0, 1], (10, 10)) self.labels = SemanticSegmentationLabels() self.labels.set_label_arr(self.windows[0], self.label_arr0) self.labels.set_label_arr(self.windows[1], self.label_arr1)
def test_get_overlapping(self): window = Box.make_square(0, 0, 2.01) labels = ObjectDetectionLabels.get_overlapping(self.labels, window) labels.assert_equal(self.labels) window = Box.make_square(0, 0, 3) labels = ObjectDetectionLabels.get_overlapping( self.labels, window, ioa_thresh=0.5) npboxes = np.array([[0., 0., 2., 2.]]) class_ids = np.array([1]) scores = np.array([0.9]) expected_labels = ObjectDetectionLabels( npboxes, class_ids, scores=scores) labels.assert_equal(expected_labels) window = Box.make_square(0, 0, 3) labels = ObjectDetectionLabels.get_overlapping( self.labels, window, ioa_thresh=0.1, clip=True) expected_npboxes = np.array([ [0., 0., 2., 2.], [2., 2., 3., 3.], ]) expected_labels = ObjectDetectionLabels( expected_npboxes, self.class_ids, scores=self.scores) labels.assert_equal(expected_labels)
def read_labels(geojson, extent=None) -> ChipClassificationLabels: """Convert GeoJSON to ChipClassificationLabels. If the GeoJSON already contains a grid of cells, then it can be constructed in a straightforward manner without having to infer the class of cells. If extent is given, only labels that intersect with the extent are returned. Args: geojson: dict in normalized GeoJSON format (see VectorSource) extent: Box in pixel coords Returns: ChipClassificationLabels """ labels = ChipClassificationLabels() for f in geojson['features']: geom = shape(f['geometry']) (xmin, ymin, xmax, ymax) = geom.bounds cell = Box(ymin, xmin, ymax, xmax) if extent is not None and not cell.to_shapely().intersects( extent.to_shapely()): continue props = f['properties'] class_id = props['class_id'] scores = props.get('scores') labels.set_cell(cell, class_id, scores) return labels
def test_make_eroded(self): max_extent = Box.make_square(0, 0, 10) box = Box(1, 1, 3, 4) buffer_size = erosion_size = 1 eroded_box = box.make_buffer(buffer_size, max_extent) \ .make_eroded(erosion_size) self.assertEqual(eroded_box, box)
def setUp(self): self.windows = [Box.make_square(0, 0, 10), Box.make_square(0, 10, 10)] self.label_arr0 = np.random.choice([0, 1], (10, 10)) self.label_arr1 = np.random.choice([0, 1], (10, 10)) self.labels = SemanticSegmentationDiscreteLabels() self.labels[self.windows[0]] = self.label_arr0 self.labels[self.windows[1]] = self.label_arr1
def test_nonidentical_extents_and_resolutions(self): img_path_1 = data_file_path( 'multi_raster_source/const_100_600x600.tiff') img_path_2 = data_file_path('multi_raster_source/const_175_60x60.tiff') img_path_3 = data_file_path('multi_raster_source/const_250_6x6.tiff') source_1 = RasterioSourceConfig(uris=[img_path_1], channel_order=[0]) source_2 = RasterioSourceConfig(uris=[img_path_2], channel_order=[0]) source_3 = RasterioSourceConfig(uris=[img_path_3], channel_order=[0]) cfg = MultiRasterSourceConfig(raster_sources=[ SubRasterSourceConfig(raster_source=source_1, target_channels=[0]), SubRasterSourceConfig(raster_source=source_2, target_channels=[1]), SubRasterSourceConfig(raster_source=source_3, target_channels=[2]) ]) rs = cfg.build(tmp_dir=self.tmp_dir) with rs.activate(): for get_chip_fn in [rs._get_chip, rs.get_chip]: ch_1_only = get_chip_fn(Box(0, 0, 10, 10)) self.assertEqual(tuple(ch_1_only.reshape(-1, 3).mean(axis=0)), (100, 0, 0)) ch_2_only = get_chip_fn(Box(0, 600, 10, 600 + 10)) self.assertEqual(tuple(ch_2_only.reshape(-1, 3).mean(axis=0)), (0, 175, 0)) ch_3_only = get_chip_fn(Box(600, 0, 600 + 10, 10)) self.assertEqual(tuple(ch_3_only.reshape(-1, 3).mean(axis=0)), (0, 0, 250)) full_img = get_chip_fn(Box(0, 0, 600, 600)) self.assertEqual(set(np.unique(full_img[..., 0])), {100}) self.assertEqual(set(np.unique(full_img[..., 1])), {0, 175}) self.assertEqual(set(np.unique(full_img[..., 2])), {0, 250})
def setUp(self): self.crs_transformer = DoubleCRSTransformer() self.geojson = { 'type': 'FeatureCollection', 'features': [{ 'type': 'Feature', 'geometry': { 'type': 'MultiPolygon', 'coordinates': [[[[0., 0.], [0., 2.], [2., 2.], [2., 0.], [0., 0.]]]] }, 'properties': { 'class_name': 'car', 'class_id': 1, 'score': 0.0 } }, { 'type': 'Feature', 'geometry': { 'type': 'Polygon', 'coordinates': [[[2., 2.], [2., 4.], [4., 4.], [4., 2.], [2., 2.]]] }, 'properties': { 'score': 0.0, 'class_name': 'house', 'class_id': 2 } }] } self.class_map = ClassMap([ClassItem(1, 'car'), ClassItem(2, 'house')]) class MockTaskConfig(): def __init__(self, class_map): self.class_map = class_map self.task_config = MockTaskConfig(self.class_map) self.box1 = Box.make_square(0, 0, 4) self.box2 = Box.make_square(4, 4, 4) self.class_id1 = 1 self.class_id2 = 2 self.background_class_id = 3 geoms = [] for f in self.geojson['features']: g = shape(f['geometry']) g.class_id = f['properties']['class_id'] geoms.append(g) self.str_tree = STRtree(geoms) self.file_name = 'labels.json' self.temp_dir = RVConfig.get_tmp_dir() self.uri = os.path.join(self.temp_dir.name, self.file_name) json_to_file(self.geojson, self.uri)
def get_extent(self): h, w = self.height, self.width if self.extent_crop is not None: skip_top, skip_left, skip_bottom, skip_right = self.extent_crop ymin, xmin = int(h * skip_top), int(w * skip_left) ymax, xmax = h - int(h * skip_bottom), w - int(w * skip_right) return Box(ymin, xmin, ymax, xmax) return Box(0, 0, h, w)
def make_ground_truth_labels(self): size = 100 nw = Box.make_square(0, 0, size) ne = Box.make_square(0, 200, size) se = Box.make_square(200, 200, size) sw = Box.make_square(200, 0, size) npboxes = Box.to_npboxes([nw, ne, se, sw]) class_ids = np.array([1, 1, 2, 2]) return ObjectDetectionLabels(npboxes, class_ids)
def test_filter_by_aoi(self): windows = [Box.make_square(0, 0, 2), Box.make_square(0, 2, 2)] aoi_polygons = [Box.make_square(0, 0, 3).to_shapely()] filt_windows = Box.filter_by_aoi(windows, aoi_polygons, within=False) self.assertListEqual(filt_windows, windows) filt_windows = Box.filter_by_aoi(windows, aoi_polygons, within=True) self.assertListEqual(filt_windows, windows[0:1])
def test_make_random_square(self): window = Box(5, 5, 15, 15) size = 5 nb_tests = 100 for _ in range(nb_tests): box = window.make_random_square(size) self.assertEqual(box.get_width(), box.get_height()) self.assertEqual(box.get_width(), size) self.assertTrue(window.to_shapely().contains(box.to_shapely()))
def setUp(self): self.labels = ChipClassificationLabels() self.cell1 = Box.make_square(0, 0, 2) self.class_id1 = 1 self.labels.set_cell(self.cell1, self.class_id1) self.cell2 = Box.make_square(0, 2, 2) self.class_id2 = 2 self.labels.set_cell(self.cell2, self.class_id2)
def make_predicted_labels(self): size = 100 # Predicted labels are only there for three of the ground truth boxes, # and are offset by 10 pixels. nw = Box.make_square(10, 0, size) ne = Box.make_square(10, 200, size) se = Box.make_square(210, 200, size) npboxes = Box.to_npboxes([nw, ne, se]) class_ids = np.array([1, 1, 2]) scores = np.ones(class_ids.shape) return ObjectDetectionLabels(npboxes, class_ids, scores=scores)
def setUp(self): self.crs_transformer = DoubleCRSTransformer() self.geojson = { 'type': 'FeatureCollection', 'features': [{ 'type': 'Feature', 'geometry': { 'type': 'MultiPolygon', 'coordinates': [[[[0., 0.], [0., 2.], [2., 2.], [2., 0.], [0., 0.]]]] }, 'properties': { 'class_name': 'car', 'class_id': 0, 'score': 0.0 } }, { 'type': 'Feature', 'geometry': { 'type': 'Polygon', 'coordinates': [[[2., 2.], [2., 4.], [4., 4.], [4., 2.], [2., 2.]]] }, 'properties': { 'score': 0.0, 'class_name': 'house', 'class_id': 1 } }] } self.class_config = ClassConfig(names=['car', 'house']) self.box1 = Box.make_square(0, 0, 4) self.box2 = Box.make_square(4, 4, 4) self.class_id1 = 0 self.class_id2 = 1 self.background_class_id = 2 geoms = [] for f in self.geojson['features']: g = shape(f['geometry']) g.class_id = f['properties']['class_id'] geoms.append(g) self.str_tree = STRtree(geoms) self.file_name = 'labels.json' self.tmp_dir = rv_config.get_tmp_dir() self.uri = os.path.join(self.tmp_dir.name, self.file_name) json_to_file(self.geojson, self.uri)
def test_make_buffer(self): buffer_size = 1 max_extent = Box.make_square(0, 0, 3) buffer_box = Box(0, 0, 3, 3) output_buffer_box = self.box.make_buffer(buffer_size, max_extent) self.assertEqual(output_buffer_box, buffer_box) buffer_size = 0.5 max_extent = Box.make_square(0, 0, 5) buffer_box = Box(0, 0, 3, 5) output_buffer_box = self.box.make_buffer(buffer_size, max_extent) self.assertEqual(output_buffer_box, buffer_box)
def test_filter_by_aoi(self): aois = [Box.make_square(0, 0, 2).to_shapely()] filt_labels = self.labels.filter_by_aoi(aois) npboxes = np.array([[0., 0., 2., 2.]]) class_ids = np.array([1]) scores = np.array([0.9]) exp_labels = ObjectDetectionLabels(npboxes, class_ids, scores=scores) self.assertEqual(filt_labels, exp_labels) aois = [Box.make_square(4, 4, 2).to_shapely()] filt_labels = self.labels.filter_by_aoi(aois) exp_labels = ObjectDetectionLabels.make_empty() self.assertEqual(filt_labels, exp_labels)
def test_filter_by_aoi(self): aois = [Box.make_square(0, 0, 2).to_shapely()] filt_labels = self.labels.filter_by_aoi(aois) exp_labels = ChipClassificationLabels() cell1 = Box.make_square(0, 0, 2) class_id1 = 1 exp_labels.set_cell(cell1, class_id1) self.assertEqual(filt_labels, exp_labels) aois = [Box.make_square(4, 4, 2).to_shapely()] filt_labels = self.labels.filter_by_aoi(aois) exp_labels = ChipClassificationLabels() self.assertEqual(filt_labels, exp_labels)
def setUp(self): self.windows = [Box.make_square(0, 0, 10), Box.make_square(0, 10, 10)] self.label_arr0 = np.random.choice([1, 2], (10, 10)) self.label_arr1 = np.random.choice([1, 2], (10, 10)) def label_fn(window): if window == self.windows[0]: return self.label_arr0.copy() elif window == self.windows[1]: return self.label_arr1.copy() else: raise ValueError('Unknown window: {}'.format(window)) self.label_fn = label_fn self.labels = SemanticSegmentationLabels(self.windows, label_fn)
def filter_windows(windows: Sequence[Box]) -> List[Box]: """Filter out chips that (1) are outside the AOI (2) only consist of null labels (3) have NODATA proportion >= chip_nodata_threshold """ total_windows = len(windows) if scene.aoi_polygons: windows = Box.filter_by_aoi(windows, scene.aoi_polygons) log.info(f'AOI filtering: {len(windows)}/{total_windows} ' 'chips accepted') filt_windows = [] for w in windows: chip = raster_source.get_chip(w) nodata_below_thresh = nodata_below_threshold( chip, chip_nodata_threshold, nodata_val=0) label_arr = label_source.get_labels(w).get_label_arr(w) null_labels = label_arr == class_config.get_null_class_id() if not np.all(null_labels) and nodata_below_thresh: filt_windows.append(w) log.info('Label and NODATA filtering: ' f'{len(filt_windows)}/{len(windows)} chips accepted') windows = filt_windows return windows
def test_get_with_aoi(self): aoi_polygons = [Box.make_square(5, 15, 2).to_shapely()] exp_label_arr = self.label_arr1.copy() mask = np.zeros(exp_label_arr.shape) mask[5:7, 5:7] = 1 exp_label_arr = exp_label_arr * mask labels = self.labels.filter_by_aoi(aoi_polygons) label_arr = labels.get_label_arr(self.windows[1]) np.testing.assert_array_equal(label_arr, exp_label_arr) # Set clip_extent clip_extent = Box(0, 0, 10, 18) label_arr = labels.get_label_arr(self.windows[1], clip_extent=clip_extent) np.testing.assert_array_equal(label_arr, exp_label_arr[:, 0:8])
def __init__(self, uris, raster_transformers, tmp_dir, channel_order=None, x_shift=0.0, y_shift=0.0): """Constructor. This RasterSource can read any file that can be opened by Rasterio/GDAL including georeferenced formats such as GeoTIFF and non-georeferenced formats such as JPG. See https://www.gdal.org/formats_list.html for more details. If channel_order is None, then use non-alpha channels. This also sets any masked or NODATA pixel values to be zeros. Args: channel_order: list of indices of channels to extract from raw imagery """ self.uris = uris self.tmp_dir = tmp_dir self.image_tmp_dir = None self.image_dataset = None self.x_shift = x_shift self.y_shift = y_shift self.do_shift = self.x_shift != 0.0 or self.y_shift != 0.0 num_channels = None # Activate in order to get information out of the raster with self.activate(): num_channels = self.image_dataset.count if channel_order is None: colorinterp = self.image_dataset.colorinterp if colorinterp: channel_order = [ i for i, color_interp in enumerate(colorinterp) if color_interp != ColorInterp.alpha ] else: channel_order = list(range(0, num_channels)) self.validate_channel_order(channel_order, num_channels) mask_flags = self.image_dataset.mask_flag_enums self.is_masked = any( [m for m in mask_flags if m != MaskFlags.all_valid]) self.height = self.image_dataset.height self.width = self.image_dataset.width # Get 1x1 chip and apply raster transformers to test dtype. test_chip = self.get_raw_chip(Box.make_square(0, 0, 1)) test_chip = test_chip[:, :, channel_order] for transformer in raster_transformers: test_chip = transformer.transform(test_chip, channel_order) self.dtype = test_chip.dtype self._set_crs_transformer() super().__init__(channel_order, num_channels, raster_transformers)
def _filter_window_by_aoi(self, window: Box, aoi_polygons: list, null_class_id: int) -> None: window_geom = window.to_shapely() label_arr = self[window] # For each aoi_polygon, intersect with window, and # put in window frame of reference. window_aois = [] for aoi in aoi_polygons: window_aoi = aoi.intersection(window_geom) if not window_aoi.is_empty: def transform_shape(x, y, z=None): return (x - window.xmin, y - window.ymin) window_aoi = transform(transform_shape, window_aoi) window_aois.append(window_aoi) # If window does't overlap with any AOI, then it won't be in # new_labels. if window_aois: # If window intersects with AOI, set pixels outside the # AOI polygon to 0 so they are ignored during eval. mask = rasterize([(p, 0) for p in window_aois], out_shape=label_arr.shape[-2:], fill=1, dtype=np.uint8) mask = mask.astype(np.bool) self.mask_fill(window, mask, null_class_id) else: del self[window]
def init_windows(self) -> None: """Pre-compute windows.""" windows = self.scene.raster_source.get_extent().get_windows( chip_sz=self.size, stride=self.stride, padding=self.padding) if len(self.scene.aoi_polygons) > 0: windows = Box.filter_by_aoi(windows, self.scene.aoi_polygons) self.windows = windows
def test_subscripting(self): box = Box(1, 2, 3, 4) self.assertEqual(box[0], 1) self.assertEqual(box[1], 2) self.assertEqual(box[2], 3) self.assertEqual(box[3], 4) self.assertRaises(IndexError, lambda: box[4])
def test_constructor_save(self): # Read it, write it using label_store, read it again, and compare. extent = Box.make_square(0, 0, 10) msg = rv.LabelStoreConfig.builder(rv.CHIP_CLASSIFICATION_GEOJSON) \ .with_uri(self.uri) \ .build().to_proto() config = rv.LabelStoreConfig.builder(rv.CHIP_CLASSIFICATION_GEOJSON) \ .from_proto(msg).build() label_store = config.create_store(self.task_config, extent, self.crs_transformer, self.temp_dir.name) labels1 = label_store.get_labels() new_uri = os.path.join(self.temp_dir.name, 'test_save_reload.json') msg = rv.LabelStoreConfig.builder(rv.CHIP_CLASSIFICATION_GEOJSON) \ .with_uri(new_uri) \ .build().to_proto() config = rv.LabelStoreConfig.builder(rv.CHIP_CLASSIFICATION_GEOJSON) \ .from_proto(msg).build() label_store = config.create_store(self.task_config, extent, self.crs_transformer, self.temp_dir.name) label_store.save(labels1) labels2 = label_store.get_labels() self.assertDictEqual(labels1.cell_to_class_id, labels2.cell_to_class_id)
def test_normalized_to_local(self): norm_npboxes = np.array([[0., 0., 0.2, 0.02], [0.2, 0.02, 0.4, 0.04]]) window = Box(0, 0, 10, 100) local_npboxes = ObjectDetectionLabels.normalized_to_local( norm_npboxes, window) expected_local_npboxes = np.array([[0., 0., 2., 2.], [2., 2., 4., 4.]]) np.testing.assert_array_equal(local_npboxes, expected_local_npboxes)
def test_global_to_local(self): global_npboxes = np.array([[10., 10., 12., 12.], [12., 12., 14., 14.]]) window = Box.make_square(10, 10, 10) local_npboxes = ObjectDetectionLabels.global_to_local( global_npboxes, window) expected_local_npboxes = np.array([[0., 0., 2., 2.], [2., 2., 4., 4.]]) np.testing.assert_array_equal(local_npboxes, expected_local_npboxes)