Repository: azavea/raster-vision

Issue:
Add options for 'building' mode feature extraction



Discussion:

Resolving patch:
diff --git a/rastervision/data/label_store/semantic_segmentation_raster_store.py b/rastervision/data/label_store/semantic_segmentation_raster_store.py
--- a/rastervision/data/label_store/semantic_segmentation_raster_store.py
+++ b/rastervision/data/label_store/semantic_segmentation_raster_store.py
@@ -27,7 +27,8 @@ def __init__(self,
             extent: (Box) The extent of the scene
             crs_transformer: (CRSTransformer)
             tmp_dir: (str) temp directory to use
-            vector_output: Array of dictionaries containing vectorization options
+            vector_output: (None or array of dicts) containing vectorifiction
+                configuration information
             class_map: (ClassMap) with color values used to convert class ids to
                 RGB values
 
@@ -159,8 +160,15 @@ def save(self, labels):
                     class_mask = denoise.denoise(class_mask, denoise_radius)
 
                 if uri and mode == 'buildings':
+                    options = vo['building_options']
                     geojson = vectorification.geojson_from_mask(
-                        mask=class_mask, transform=transform, mode=mode)
+                        mask=class_mask,
+                        transform=transform,
+                        mode=mode,
+                        min_aspect_ratio=options['min_aspect_ratio'],
+                        min_area=options['min_area'],
+                        width_factor=options['element_width_factor'],
+                        thickness=options['element_thickness'])
                 elif uri and mode == 'polygons':
                     geojson = vectorification.geojson_from_mask(
                         mask=class_mask, transform=transform, mode=mode)
diff --git a/rastervision/data/label_store/semantic_segmentation_raster_store_config.py b/rastervision/data/label_store/semantic_segmentation_raster_store_config.py
--- a/rastervision/data/label_store/semantic_segmentation_raster_store_config.py
+++ b/rastervision/data/label_store/semantic_segmentation_raster_store_config.py
@@ -6,6 +6,8 @@
     LabelStoreConfig, LabelStoreConfigBuilder, SemanticSegmentationRasterStore)
 from rastervision.protos.label_store_pb2 import LabelStoreConfig as LabelStoreConfigMsg
 
+VectorOutput = LabelStoreConfigMsg.SemanticSegmentationRasterStore.VectorOutput
+
 
 class SemanticSegmentationRasterStoreConfig(LabelStoreConfig):
     def __init__(self, uri=None, vector_output=[], rgb=False):
@@ -15,19 +17,62 @@ def __init__(self, uri=None, vector_output=[], rgb=False):
         self.rgb = rgb
 
     def to_proto(self):
+        """Turn this configuration into a ProtoBuf message.
+
+        The fields in the message are as follows:
+            - `denoise` gives the radius of the structural element
+              used to remove high-frequency signals from the image.
+            - `uri` is the location where vector output should be
+              written
+            - `mode` is the vectorification mode (currently only
+              "polygons" and "buildings" are acceptable values).
+            - `class_id` specifies the predication class that is to
+              turned into vectors
+            - `building_options` communicates options useful for
+              vectorification of building predictions (it is intended
+              to break-up clusters of buildings):
+                - `min_aspect_ratio` is the ratio between length and
+                  height (or height and length) of anything that can
+                  be considered to be a cluster of buildings.  The
+                  goal is to distinguish between rows of buildings and
+                  (say) a single building.
+                - `min_area` is the minimum area of anything that can
+                  be considered to be a cluster of buildings.  The
+                  goal is to distinguish between buildings and
+                  artifacts.
+                - `element_width_factor` is the width of the
+                  structural element used to break building clusters
+                  as a fraction of the width of the cluster.
+                - `element_thickness` is the thickness of the
+                  structural element that is used to break building
+                  clusters.
+        """
         msg = super().to_proto()
         if self.uri:
             msg.semantic_segmentation_raster_store.uri = self.uri
         if self.vector_output:
             ar = []
             for vo in self.vector_output:
-                msg2 = LabelStoreConfigMsg.SemanticSegmentationRasterStore.VectorOutput(
-                )
-                msg2.denoise = vo['denoise'] if 'denoise' in vo.keys() else 0
-                msg2.uri = vo['uri'] if 'uri' in vo.keys() else ''
-                msg2.mode = vo['mode']
-                msg2.class_id = vo['class_id']
-                ar.append(msg2)
+                vo_msg = VectorOutput()
+                vo_msg.denoise = vo['denoise'] if 'denoise' in vo.keys() else 0
+                vo_msg.uri = vo['uri'] if 'uri' in vo.keys() else ''
+                vo_msg.mode = vo['mode']
+                vo_msg.class_id = vo['class_id']
+                if 'building_options' in vo.keys():
+                    options = vo['building_options']
+                else:
+                    options = {}
+                bldg_msg = vo_msg.building_options
+                if 'min_aspect_ratio' in options.keys():
+                    bldg_msg.min_aspect_ratio = options['min_aspect_ratio']
+                if 'min_area' in options.keys() and options['min_area']:
+                    bldg_msg.min_area = options['min_area']
+                if 'element_width_factor' in options.keys():
+                    bldg_msg.element_width_factor = options[
+                        'element_width_factor']
+                if 'element_thickness' in options.keys():
+                    bldg_msg.element_thickness = options['element_thickness']
+                ar.append(vo_msg)
             msg.semantic_segmentation_raster_store.vector_output.extend(ar)
         msg.semantic_segmentation_raster_store.rgb = self.rgb
         return msg
@@ -119,10 +164,10 @@ def __init__(self, prev=None):
     def from_proto(self, msg):
         uri = msg.semantic_segmentation_raster_store.uri
         rgb = msg.semantic_segmentation_raster_store.rgb
-        vo = msg.semantic_segmentation_raster_store.vector_output
+        vo_msg = msg.semantic_segmentation_raster_store.vector_output
 
         return self.with_uri(uri) \
-                   .with_vector_output(vo) \
+                   .with_vector_output(vo_msg) \
                    .with_rgb(rgb)
 
     def with_uri(self, uri):
@@ -159,12 +204,23 @@ def with_vector_output(self, vector_output):
             for vo in vector_output:
                 ar.append(vo.copy())
         else:
-            for vo in vector_output:
+            for vo_msg in vector_output:
+                bldg_msg = vo_msg.building_options
                 ar.append({
-                    'denoise': vo.denoise,
-                    'uri': vo.uri,
-                    'mode': vo.mode,
-                    'class_id': vo.class_id
+                    'denoise': vo_msg.denoise,
+                    'uri': vo_msg.uri,
+                    'mode': vo_msg.mode,
+                    'class_id': vo_msg.class_id,
+                    'building_options': {
+                        'min_aspect_ratio':
+                        bldg_msg.min_aspect_ratio,
+                        'min_area':
+                        bldg_msg.min_area if bldg_msg.min_area > 0 else None,
+                        'element_width_factor':
+                        bldg_msg.element_width_factor,
+                        'element_thickness':
+                        bldg_msg.element_thickness,
+                    },
                 })
 
         b.config['vector_output'] = ar
diff --git a/rastervision/protos/label_store_pb2.py b/rastervision/protos/label_store_pb2.py
--- a/rastervision/protos/label_store_pb2.py
+++ b/rastervision/protos/label_store_pb2.py
@@ -20,7 +20,7 @@
   name='rastervision/protos/label_store.proto',
   package='rv.protos',
   syntax='proto2',
-  serialized_pb=_b('\n%rastervision/protos/label_store.proto\x12\trv.protos\x1a\x1cgoogle/protobuf/struct.proto\"\xe1\x03\n\x10LabelStoreConfig\x12\x12\n\nstore_type\x18\x01 \x02(\t\x12\r\n\x03uri\x18\x02 \x01(\tH\x00\x12i\n\"semantic_segmentation_raster_store\x18\x03 \x01(\x0b\x32;.rv.protos.LabelStoreConfig.SemanticSegmentationRasterStoreH\x00\x12\x30\n\rcustom_config\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x1a\xf6\x01\n\x1fSemanticSegmentationRasterStore\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x12\n\x03rgb\x18\x02 \x01(\x08:\x05\x66\x61lse\x12_\n\rvector_output\x18\x03 \x03(\x0b\x32H.rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.VectorOutput\x1aQ\n\x0cVectorOutput\x12\x12\n\x07\x64\x65noise\x18\x01 \x01(\x05:\x01\x30\x12\r\n\x03uri\x18\x02 \x01(\t:\x00\x12\x0c\n\x04mode\x18\x03 \x02(\t\x12\x10\n\x08\x63lass_id\x18\x04 \x02(\x05\x42\x14\n\x12label_store_config')
+  serialized_pb=_b('\n%rastervision/protos/label_store.proto\x12\trv.protos\x1a\x1cgoogle/protobuf/struct.proto\"\xd5\x05\n\x10LabelStoreConfig\x12\x12\n\nstore_type\x18\x01 \x02(\t\x12\r\n\x03uri\x18\x02 \x01(\tH\x00\x12i\n\"semantic_segmentation_raster_store\x18\x03 \x01(\x0b\x32;.rv.protos.LabelStoreConfig.SemanticSegmentationRasterStoreH\x00\x12\x30\n\rcustom_config\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x1a\xea\x03\n\x1fSemanticSegmentationRasterStore\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x12\n\x03rgb\x18\x02 \x01(\x08:\x05\x66\x61lse\x12_\n\rvector_output\x18\x03 \x03(\x0b\x32H.rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.VectorOutput\x1a\x89\x01\n\x0f\x42uildingOptions\x12\x1f\n\x10min_aspect_ratio\x18\x01 \x01(\x02:\x05\x31.618\x12\x10\n\x08min_area\x18\x02 \x01(\x02\x12!\n\x14\x65lement_width_factor\x18\x03 \x01(\x02:\x03\x30.5\x12 \n\x11\x65lement_thickness\x18\x04 \x01(\x02:\x05\x30.001\x1a\xb8\x01\n\x0cVectorOutput\x12\x12\n\x07\x64\x65noise\x18\x01 \x01(\x05:\x01\x30\x12\r\n\x03uri\x18\x02 \x01(\t:\x00\x12\x0c\n\x04mode\x18\x03 \x02(\t\x12\x10\n\x08\x63lass_id\x18\x04 \x02(\x05\x12\x65\n\x10\x62uilding_options\x18\x05 \x01(\x0b\x32K.rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptionsB\x14\n\x12label_store_config')
   ,
   dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,])
 _sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -28,6 +28,57 @@
 
 
 
+_LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_BUILDINGOPTIONS = _descriptor.Descriptor(
+  name='BuildingOptions',
+  full_name='rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptions',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='min_aspect_ratio', full_name='rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptions.min_aspect_ratio', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=True, default_value=float(1.618),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='min_area', full_name='rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptions.min_area', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='element_width_factor', full_name='rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptions.element_width_factor', index=2,
+      number=3, type=2, cpp_type=6, label=1,
+      has_default_value=True, default_value=float(0.5),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='element_thickness', full_name='rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptions.element_thickness', index=3,
+      number=4, type=2, cpp_type=6, label=1,
+      has_default_value=True, default_value=float(0.001),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=462,
+  serialized_end=599,
+)
+
 _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_VECTOROUTPUT = _descriptor.Descriptor(
   name='VectorOutput',
   full_name='rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.VectorOutput',
@@ -63,6 +114,13 @@
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
+    _descriptor.FieldDescriptor(
+      name='building_options', full_name='rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.VectorOutput.building_options', index=4,
+      number=5, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
   ],
   extensions=[
   ],
@@ -75,8 +133,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=461,
-  serialized_end=542,
+  serialized_start=602,
+  serialized_end=786,
 )
 
 _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE = _descriptor.Descriptor(
@@ -110,7 +168,7 @@
   ],
   extensions=[
   ],
-  nested_types=[_LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_VECTOROUTPUT, ],
+  nested_types=[_LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_BUILDINGOPTIONS, _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_VECTOROUTPUT, ],
   enum_types=[
   ],
   options=None,
@@ -120,7 +178,7 @@
   oneofs=[
   ],
   serialized_start=296,
-  serialized_end=542,
+  serialized_end=786,
 )
 
 _LABELSTORECONFIG = _descriptor.Descriptor(
@@ -174,9 +232,11 @@
       index=0, containing_type=None, fields=[]),
   ],
   serialized_start=83,
-  serialized_end=564,
+  serialized_end=808,
 )
 
+_LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_BUILDINGOPTIONS.containing_type = _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE
+_LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_VECTOROUTPUT.fields_by_name['building_options'].message_type = _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_BUILDINGOPTIONS
 _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_VECTOROUTPUT.containing_type = _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE
 _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE.fields_by_name['vector_output'].message_type = _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_VECTOROUTPUT
 _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE.containing_type = _LABELSTORECONFIG
@@ -197,6 +257,13 @@
 
   SemanticSegmentationRasterStore = _reflection.GeneratedProtocolMessageType('SemanticSegmentationRasterStore', (_message.Message,), dict(
 
+    BuildingOptions = _reflection.GeneratedProtocolMessageType('BuildingOptions', (_message.Message,), dict(
+      DESCRIPTOR = _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_BUILDINGOPTIONS,
+      __module__ = 'rastervision.protos.label_store_pb2'
+      # @@protoc_insertion_point(class_scope:rv.protos.LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptions)
+      ))
+    ,
+
     VectorOutput = _reflection.GeneratedProtocolMessageType('VectorOutput', (_message.Message,), dict(
       DESCRIPTOR = _LABELSTORECONFIG_SEMANTICSEGMENTATIONRASTERSTORE_VECTOROUTPUT,
       __module__ = 'rastervision.protos.label_store_pb2'
@@ -214,6 +281,7 @@
   ))
 _sym_db.RegisterMessage(LabelStoreConfig)
 _sym_db.RegisterMessage(LabelStoreConfig.SemanticSegmentationRasterStore)
+_sym_db.RegisterMessage(LabelStoreConfig.SemanticSegmentationRasterStore.BuildingOptions)
 _sym_db.RegisterMessage(LabelStoreConfig.SemanticSegmentationRasterStore.VectorOutput)
 
 


Test patch:
diff --git a/integration_tests/semantic_segmentation_tests/experiment.py b/integration_tests/semantic_segmentation_tests/experiment.py
--- a/integration_tests/semantic_segmentation_tests/experiment.py
+++ b/integration_tests/semantic_segmentation_tests/experiment.py
@@ -57,7 +57,10 @@ def get_path(part):
 
         vector_output = [{
             'mode': 'buildings',
-            'class_id': 1
+            'class_id': 1,
+            'building_options': {
+                'element_width_factor': 0.51
+            }
         }, {
             'denoise': 50,
             'mode': 'polygons',