We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow `neg_ratio` to be None in `ObjectDetectionGeoDataWindowConfig`
## 🚀 Feature

See the following discussion on Gitter: https://gitter.im/azavea/raster-vision?at=6298bff6ef00bd1dc60fc7ee

`ObjectDetectionGeoDataWindowConfig` should allow `neg_ratio` to be `None` since `ObjectDetectionRandomWindowGeoDataset` allows it.

## Motivation

It's a feature that already exists. This will allow users to actually make use of it.

## Pitch

`ObjectDetectionGeoDataWindowConfig.neg_ratio`'s type hint will need to be changed to `Optional[float]`.

## Alternatives

N/A

## Additional context

N/A

Allow `neg_ratio` to be None in `ObjectDetectionGeoDataWindowConfig`
## 🚀 Feature

See the following discussion on Gitter: https://gitter.im/azavea/raster-vision?at=6298bff6ef00bd1dc60fc7ee

`ObjectDetectionGeoDataWindowConfig` should allow `neg_ratio` to be `None` since `ObjectDetectionRandomWindowGeoDataset` allows it.

## Motivation

It's a feature that already exists. This will allow users to actually make use of it.

## Pitch

`ObjectDetectionGeoDataWindowConfig.neg_ratio`'s type hint will need to be changed to `Optional[float]`.

## Alternatives

N/A

## Additional context

N/A


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py`
Content:
```
1 from typing import TYPE_CHECKING, Optional, Union
2 from enum import Enum
3 from os.path import join
4 import logging
5 
6 import albumentations as A
7 
8 from torchvision.models.detection.backbone_utils import resnet_fpn_backbone
9 
10 from rastervision.core.data import Scene
11 from rastervision.pipeline.config import (Config, register_config, Field,
12                                           validator, ConfigError)
13 from rastervision.pytorch_learner.learner_config import (
14     LearnerConfig, ModelConfig, Backbone, ImageDataConfig, GeoDataConfig,
15     GeoDataWindowMethod, GeoDataWindowConfig)
16 from rastervision.pytorch_learner.dataset import (
17     ObjectDetectionImageDataset, ObjectDetectionSlidingWindowGeoDataset,
18     ObjectDetectionRandomWindowGeoDataset)
19 from rastervision.pytorch_learner.utils import adjust_conv_channels
20 from torchvision.models.detection.faster_rcnn import FasterRCNN
21 
22 if TYPE_CHECKING:
23     from rastervision.pytorch_learner.learner_config import SolverConfig
24 
25 log = logging.getLogger(__name__)
26 
27 DEFAULT_BBOX_PARAMS = A.BboxParams(
28     format='albumentations', label_fields=['category_id'])
29 
30 
31 class ObjectDetectionDataFormat(Enum):
32     coco = 'coco'
33 
34 
35 def objdet_data_config_upgrader(cfg_dict, version):
36     if version == 1:
37         cfg_dict['type_hint'] = 'object_detection_image_data'
38     return cfg_dict
39 
40 
41 @register_config('object_detection_data', upgrader=objdet_data_config_upgrader)
42 class ObjectDetectionDataConfig(Config):
43     def get_bbox_params(self):
44         return DEFAULT_BBOX_PARAMS
45 
46 
47 @register_config('object_detection_image_data')
48 class ObjectDetectionImageDataConfig(ObjectDetectionDataConfig,
49                                      ImageDataConfig):
50     data_format: ObjectDetectionDataFormat = ObjectDetectionDataFormat.coco
51 
52     def dir_to_dataset(self, data_dir: str, transform: A.BasicTransform
53                        ) -> ObjectDetectionImageDataset:
54         img_dir = join(data_dir, 'img')
55         annotation_uri = join(data_dir, 'labels.json')
56         ds = ObjectDetectionImageDataset(
57             img_dir, annotation_uri, transform=transform)
58         return ds
59 
60 
61 @register_config('object_detection_geo_data_window')
62 class ObjectDetectionGeoDataWindowConfig(GeoDataWindowConfig):
63     ioa_thresh: float = Field(
64         0.8,
65         description='When a box is partially outside of a training chip, it '
66         'is not clear if (a clipped version) of the box should be included in '
67         'the chip. If the IOA (intersection over area) of the box with the '
68         'chip is greater than ioa_thresh, it is included in the chip. '
69         'Defaults to 0.8.')
70     clip: bool = Field(
71         False,
72         description='Clip bounding boxes to window limits when retrieving '
73         'labels for a window.')
74     neg_ratio: float = Field(
75         1.0,
76         description='The ratio of negative chips (those containing no '
77         'bounding boxes) to positive chips. This can be useful if the '
78         'statistics of the background is different in positive chips. For '
79         'example, in car detection, the positive chips will always contain '
80         'roads, but no examples of rooftops since cars tend to not be near '
81         'rooftops. Defaults to 1.0.')
82     neg_ioa_thresh: float = Field(
83         0.2,
84         description='A window will be considered negative if its max IoA with '
85         'any bounding box is less than this threshold. Defaults to 0.2.')
86 
87 
88 @register_config('object_detection_geo_data')
89 class ObjectDetectionGeoDataConfig(ObjectDetectionDataConfig, GeoDataConfig):
90     def scene_to_dataset(
91             self,
92             scene: Scene,
93             transform: Optional[A.BasicTransform] = None,
94             bbox_params: Optional[A.BboxParams] = DEFAULT_BBOX_PARAMS
95     ) -> Union[ObjectDetectionSlidingWindowGeoDataset,
96                ObjectDetectionRandomWindowGeoDataset]:
97         if isinstance(self.window_opts, dict):
98             opts = self.window_opts[scene.id]
99         else:
100             opts = self.window_opts
101 
102         if opts.method == GeoDataWindowMethod.sliding:
103             ds = ObjectDetectionSlidingWindowGeoDataset(
104                 scene,
105                 size=opts.size,
106                 stride=opts.stride,
107                 padding=opts.padding,
108                 transform=transform)
109         elif opts.method == GeoDataWindowMethod.random:
110             ds = ObjectDetectionRandomWindowGeoDataset(
111                 scene,
112                 size_lims=opts.size_lims,
113                 h_lims=opts.h_lims,
114                 w_lims=opts.w_lims,
115                 out_size=opts.size,
116                 padding=opts.padding,
117                 max_windows=opts.max_windows,
118                 max_sample_attempts=opts.max_sample_attempts,
119                 bbox_params=bbox_params,
120                 ioa_thresh=opts.ioa_thresh,
121                 clip=opts.clip,
122                 neg_ratio=opts.neg_ratio,
123                 neg_ioa_thresh=opts.neg_ioa_thresh,
124                 efficient_aoi_sampling=opts.efficient_aoi_sampling,
125                 transform=transform)
126         else:
127             raise NotImplementedError()
128         return ds
129 
130 
131 @register_config('object_detection_model')
132 class ObjectDetectionModelConfig(ModelConfig):
133     backbone: Backbone = Field(
134         Backbone.resnet50,
135         description=
136         ('The torchvision.models backbone to use, which must be in the resnet* '
137          'family.'))
138 
139     @validator('backbone')
140     def only_valid_backbones(cls, v):
141         if v not in [
142                 Backbone.resnet18, Backbone.resnet34, Backbone.resnet50,
143                 Backbone.resnet101, Backbone.resnet152
144         ]:
145             raise ValueError(
146                 'The backbone for Faster-RCNN must be in the resnet* '
147                 'family.')
148         return v
149 
150     def build_default_model(self, num_classes: int, in_channels: int,
151                             img_sz: int) -> FasterRCNN:
152         """Returns a FasterRCNN model.
153 
154         Note that the model returned will have (num_classes + 2) output
155         classes. +1 for the null class (zeroth index), and another +1
156         (last index) for backward compatibility with earlier Raster Vision
157         versions.
158 
159         Returns:
160             FasterRCNN: a FasterRCNN model.
161         """
162         pretrained = self.pretrained
163         backbone_arch = self.get_backbone_str()
164         backbone = resnet_fpn_backbone(backbone_arch, pretrained)
165 
166         # default values from FasterRCNN constructor
167         image_mean = [0.485, 0.456, 0.406]
168         image_std = [0.229, 0.224, 0.225]
169 
170         if in_channels != 3:
171             extra_channels = in_channels - backbone.body['conv1'].in_channels
172 
173             # adjust channels
174             backbone.body['conv1'] = adjust_conv_channels(
175                 old_conv=backbone.body['conv1'],
176                 in_channels=in_channels,
177                 pretrained=pretrained)
178 
179             # adjust stats
180             if extra_channels < 0:
181                 image_mean = image_mean[:extra_channels]
182                 image_std = image_std[:extra_channels]
183             else:
184                 # arbitrarily set mean and stds of the new channels to
185                 # something similar to the values of the other 3 channels
186                 image_mean = image_mean + [.45] * extra_channels
187                 image_std = image_std + [.225] * extra_channels
188 
189         model = FasterRCNN(
190             backbone=backbone,
191             # +1 because torchvision detection models reserve 0 for the null
192             # class, another +1 for backward compatibility with earlier Raster
193             # Vision versions
194             num_classes=num_classes + 1 + 1,
195             # TODO we shouldn't need to pass the image size here
196             min_size=img_sz,
197             max_size=img_sz,
198             image_mean=image_mean,
199             image_std=image_std)
200         return model
201 
202 
203 @register_config('object_detection_learner')
204 class ObjectDetectionLearnerConfig(LearnerConfig):
205     data: Union[ObjectDetectionImageDataConfig, ObjectDetectionGeoDataConfig]
206     model: Optional[ObjectDetectionModelConfig]
207 
208     def build(self,
209               tmp_dir=None,
210               model_weights_path=None,
211               model_def_path=None,
212               loss_def_path=None,
213               training=True):
214         from rastervision.pytorch_learner.object_detection_learner import (
215             ObjectDetectionLearner)
216         return ObjectDetectionLearner(
217             self,
218             tmp_dir=tmp_dir,
219             model_weights_path=model_weights_path,
220             model_def_path=model_def_path,
221             loss_def_path=loss_def_path,
222             training=training)
223 
224     @validator('solver')
225     def validate_solver_config(cls, v: 'SolverConfig') -> 'SolverConfig':
226         if v.ignore_class_index is not None:
227             raise ConfigError(
228                 'ignore_last_class is not supported for Object Detection.')
229         if v.class_loss_weights is not None:
230             raise ConfigError(
231                 'class_loss_weights is currently not supported for '
232                 'Object Detection.')
233         if v.external_loss_def is not None:
234             raise ConfigError(
235                 'external_loss_def is currently not supported for '
236                 'Object Detection. Raster Vision expects object '
237                 'detection models to behave like TorchVision object detection '
238                 'models, and these models compute losses internally. So, if '
239                 'you want to use a custom loss function, you can create a '
240                 'custom model that implements that loss function and use that '
241                 'model via external_model_def. See cowc_potsdam.py for an '
242                 'example of how to use a custom object detection model.')
243         return v
244
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py b/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py
--- a/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py
+++ b/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py
@@ -71,14 +71,14 @@
         False,
         description='Clip bounding boxes to window limits when retrieving '
         'labels for a window.')
-    neg_ratio: float = Field(
-        1.0,
+    neg_ratio: Optional[float] = Field(
+        None,
         description='The ratio of negative chips (those containing no '
         'bounding boxes) to positive chips. This can be useful if the '
         'statistics of the background is different in positive chips. For '
         'example, in car detection, the positive chips will always contain '
         'roads, but no examples of rooftops since cars tend to not be near '
-        'rooftops. Defaults to 1.0.')
+        'rooftops. Defaults to None.')
     neg_ioa_thresh: float = Field(
         0.2,
         description='A window will be considered negative if its max IoA with '

{"golden_diff": "diff --git a/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py b/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py\n--- a/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py\n+++ b/rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py\n@@ -71,14 +71,14 @@\n         False,\n         description='Clip bounding boxes to window limits when retrieving '\n         'labels for a window.')\n-    neg_ratio: float = Field(\n-        1.0,\n+    neg_ratio: Optional[float] = Field(\n+        None,\n         description='The ratio of negative chips (those containing no '\n         'bounding boxes) to positive chips. This can be useful if the '\n         'statistics of the background is different in positive chips. For '\n         'example, in car detection, the positive chips will always contain '\n         'roads, but no examples of rooftops since cars tend to not be near '\n-        'rooftops. Defaults to 1.0.')\n+        'rooftops. Defaults to None.')\n     neg_ioa_thresh: float = Field(\n         0.2,\n         description='A window will be considered negative if its max IoA with '\n", "issue": "Allow `neg_ratio` to be None in `ObjectDetectionGeoDataWindowConfig`\n## \ud83d\ude80 Feature\r\n\r\nSee the following discussion on Gitter: https://gitter.im/azavea/raster-vision?at=6298bff6ef00bd1dc60fc7ee\r\n\r\n`ObjectDetectionGeoDataWindowConfig` should allow `neg_ratio` to be `None` since `ObjectDetectionRandomWindowGeoDataset` allows it.\r\n\r\n## Motivation\r\n\r\nIt's a feature that already exists. This will allow users to actually make use of it.\r\n\r\n## Pitch\r\n\r\n`ObjectDetectionGeoDataWindowConfig.neg_ratio`'s type hint will need to be changed to `Optional[float]`.\r\n\r\n## Alternatives\r\n\r\nN/A\r\n\r\n## Additional context\r\n\r\nN/A\r\n\nAllow `neg_ratio` to be None in `ObjectDetectionGeoDataWindowConfig`\n## \ud83d\ude80 Feature\r\n\r\nSee the following discussion on Gitter: https://gitter.im/azavea/raster-vision?at=6298bff6ef00bd1dc60fc7ee\r\n\r\n`ObjectDetectionGeoDataWindowConfig` should allow `neg_ratio` to be `None` since `ObjectDetectionRandomWindowGeoDataset` allows it.\r\n\r\n## Motivation\r\n\r\nIt's a feature that already exists. This will allow users to actually make use of it.\r\n\r\n## Pitch\r\n\r\n`ObjectDetectionGeoDataWindowConfig.neg_ratio`'s type hint will need to be changed to `Optional[float]`.\r\n\r\n## Alternatives\r\n\r\nN/A\r\n\r\n## Additional context\r\n\r\nN/A\r\n\n", "before_files": [{"content": "from typing import TYPE_CHECKING, Optional, Union\nfrom enum import Enum\nfrom os.path import join\nimport logging\n\nimport albumentations as A\n\nfrom torchvision.models.detection.backbone_utils import resnet_fpn_backbone\n\nfrom rastervision.core.data import Scene\nfrom rastervision.pipeline.config import (Config, register_config, Field,\n                                          validator, ConfigError)\nfrom rastervision.pytorch_learner.learner_config import (\n    LearnerConfig, ModelConfig, Backbone, ImageDataConfig, GeoDataConfig,\n    GeoDataWindowMethod, GeoDataWindowConfig)\nfrom rastervision.pytorch_learner.dataset import (\n    ObjectDetectionImageDataset, ObjectDetectionSlidingWindowGeoDataset,\n    ObjectDetectionRandomWindowGeoDataset)\nfrom rastervision.pytorch_learner.utils import adjust_conv_channels\nfrom torchvision.models.detection.faster_rcnn import FasterRCNN\n\nif TYPE_CHECKING:\n    from rastervision.pytorch_learner.learner_config import SolverConfig\n\nlog = logging.getLogger(__name__)\n\nDEFAULT_BBOX_PARAMS = A.BboxParams(\n    format='albumentations', label_fields=['category_id'])\n\n\nclass ObjectDetectionDataFormat(Enum):\n    coco = 'coco'\n\n\ndef objdet_data_config_upgrader(cfg_dict, version):\n    if version == 1:\n        cfg_dict['type_hint'] = 'object_detection_image_data'\n    return cfg_dict\n\n\n@register_config('object_detection_data', upgrader=objdet_data_config_upgrader)\nclass ObjectDetectionDataConfig(Config):\n    def get_bbox_params(self):\n        return DEFAULT_BBOX_PARAMS\n\n\n@register_config('object_detection_image_data')\nclass ObjectDetectionImageDataConfig(ObjectDetectionDataConfig,\n                                     ImageDataConfig):\n    data_format: ObjectDetectionDataFormat = ObjectDetectionDataFormat.coco\n\n    def dir_to_dataset(self, data_dir: str, transform: A.BasicTransform\n                       ) -> ObjectDetectionImageDataset:\n        img_dir = join(data_dir, 'img')\n        annotation_uri = join(data_dir, 'labels.json')\n        ds = ObjectDetectionImageDataset(\n            img_dir, annotation_uri, transform=transform)\n        return ds\n\n\n@register_config('object_detection_geo_data_window')\nclass ObjectDetectionGeoDataWindowConfig(GeoDataWindowConfig):\n    ioa_thresh: float = Field(\n        0.8,\n        description='When a box is partially outside of a training chip, it '\n        'is not clear if (a clipped version) of the box should be included in '\n        'the chip. If the IOA (intersection over area) of the box with the '\n        'chip is greater than ioa_thresh, it is included in the chip. '\n        'Defaults to 0.8.')\n    clip: bool = Field(\n        False,\n        description='Clip bounding boxes to window limits when retrieving '\n        'labels for a window.')\n    neg_ratio: float = Field(\n        1.0,\n        description='The ratio of negative chips (those containing no '\n        'bounding boxes) to positive chips. This can be useful if the '\n        'statistics of the background is different in positive chips. For '\n        'example, in car detection, the positive chips will always contain '\n        'roads, but no examples of rooftops since cars tend to not be near '\n        'rooftops. Defaults to 1.0.')\n    neg_ioa_thresh: float = Field(\n        0.2,\n        description='A window will be considered negative if its max IoA with '\n        'any bounding box is less than this threshold. Defaults to 0.2.')\n\n\n@register_config('object_detection_geo_data')\nclass ObjectDetectionGeoDataConfig(ObjectDetectionDataConfig, GeoDataConfig):\n    def scene_to_dataset(\n            self,\n            scene: Scene,\n            transform: Optional[A.BasicTransform] = None,\n            bbox_params: Optional[A.BboxParams] = DEFAULT_BBOX_PARAMS\n    ) -> Union[ObjectDetectionSlidingWindowGeoDataset,\n               ObjectDetectionRandomWindowGeoDataset]:\n        if isinstance(self.window_opts, dict):\n            opts = self.window_opts[scene.id]\n        else:\n            opts = self.window_opts\n\n        if opts.method == GeoDataWindowMethod.sliding:\n            ds = ObjectDetectionSlidingWindowGeoDataset(\n                scene,\n                size=opts.size,\n                stride=opts.stride,\n                padding=opts.padding,\n                transform=transform)\n        elif opts.method == GeoDataWindowMethod.random:\n            ds = ObjectDetectionRandomWindowGeoDataset(\n                scene,\n                size_lims=opts.size_lims,\n                h_lims=opts.h_lims,\n                w_lims=opts.w_lims,\n                out_size=opts.size,\n                padding=opts.padding,\n                max_windows=opts.max_windows,\n                max_sample_attempts=opts.max_sample_attempts,\n                bbox_params=bbox_params,\n                ioa_thresh=opts.ioa_thresh,\n                clip=opts.clip,\n                neg_ratio=opts.neg_ratio,\n                neg_ioa_thresh=opts.neg_ioa_thresh,\n                efficient_aoi_sampling=opts.efficient_aoi_sampling,\n                transform=transform)\n        else:\n            raise NotImplementedError()\n        return ds\n\n\n@register_config('object_detection_model')\nclass ObjectDetectionModelConfig(ModelConfig):\n    backbone: Backbone = Field(\n        Backbone.resnet50,\n        description=\n        ('The torchvision.models backbone to use, which must be in the resnet* '\n         'family.'))\n\n    @validator('backbone')\n    def only_valid_backbones(cls, v):\n        if v not in [\n                Backbone.resnet18, Backbone.resnet34, Backbone.resnet50,\n                Backbone.resnet101, Backbone.resnet152\n        ]:\n            raise ValueError(\n                'The backbone for Faster-RCNN must be in the resnet* '\n                'family.')\n        return v\n\n    def build_default_model(self, num_classes: int, in_channels: int,\n                            img_sz: int) -> FasterRCNN:\n        \"\"\"Returns a FasterRCNN model.\n\n        Note that the model returned will have (num_classes + 2) output\n        classes. +1 for the null class (zeroth index), and another +1\n        (last index) for backward compatibility with earlier Raster Vision\n        versions.\n\n        Returns:\n            FasterRCNN: a FasterRCNN model.\n        \"\"\"\n        pretrained = self.pretrained\n        backbone_arch = self.get_backbone_str()\n        backbone = resnet_fpn_backbone(backbone_arch, pretrained)\n\n        # default values from FasterRCNN constructor\n        image_mean = [0.485, 0.456, 0.406]\n        image_std = [0.229, 0.224, 0.225]\n\n        if in_channels != 3:\n            extra_channels = in_channels - backbone.body['conv1'].in_channels\n\n            # adjust channels\n            backbone.body['conv1'] = adjust_conv_channels(\n                old_conv=backbone.body['conv1'],\n                in_channels=in_channels,\n                pretrained=pretrained)\n\n            # adjust stats\n            if extra_channels < 0:\n                image_mean = image_mean[:extra_channels]\n                image_std = image_std[:extra_channels]\n            else:\n                # arbitrarily set mean and stds of the new channels to\n                # something similar to the values of the other 3 channels\n                image_mean = image_mean + [.45] * extra_channels\n                image_std = image_std + [.225] * extra_channels\n\n        model = FasterRCNN(\n            backbone=backbone,\n            # +1 because torchvision detection models reserve 0 for the null\n            # class, another +1 for backward compatibility with earlier Raster\n            # Vision versions\n            num_classes=num_classes + 1 + 1,\n            # TODO we shouldn't need to pass the image size here\n            min_size=img_sz,\n            max_size=img_sz,\n            image_mean=image_mean,\n            image_std=image_std)\n        return model\n\n\n@register_config('object_detection_learner')\nclass ObjectDetectionLearnerConfig(LearnerConfig):\n    data: Union[ObjectDetectionImageDataConfig, ObjectDetectionGeoDataConfig]\n    model: Optional[ObjectDetectionModelConfig]\n\n    def build(self,\n              tmp_dir=None,\n              model_weights_path=None,\n              model_def_path=None,\n              loss_def_path=None,\n              training=True):\n        from rastervision.pytorch_learner.object_detection_learner import (\n            ObjectDetectionLearner)\n        return ObjectDetectionLearner(\n            self,\n            tmp_dir=tmp_dir,\n            model_weights_path=model_weights_path,\n            model_def_path=model_def_path,\n            loss_def_path=loss_def_path,\n            training=training)\n\n    @validator('solver')\n    def validate_solver_config(cls, v: 'SolverConfig') -> 'SolverConfig':\n        if v.ignore_class_index is not None:\n            raise ConfigError(\n                'ignore_last_class is not supported for Object Detection.')\n        if v.class_loss_weights is not None:\n            raise ConfigError(\n                'class_loss_weights is currently not supported for '\n                'Object Detection.')\n        if v.external_loss_def is not None:\n            raise ConfigError(\n                'external_loss_def is currently not supported for '\n                'Object Detection. Raster Vision expects object '\n                'detection models to behave like TorchVision object detection '\n                'models, and these models compute losses internally. So, if '\n                'you want to use a custom loss function, you can create a '\n                'custom model that implements that loss function and use that '\n                'model via external_model_def. See cowc_potsdam.py for an '\n                'example of how to use a custom object detection model.')\n        return v\n", "path": "rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py"}], "after_files": [{"content": "from typing import TYPE_CHECKING, Optional, Union\nfrom enum import Enum\nfrom os.path import join\nimport logging\n\nimport albumentations as A\n\nfrom torchvision.models.detection.backbone_utils import resnet_fpn_backbone\n\nfrom rastervision.core.data import Scene\nfrom rastervision.pipeline.config import (Config, register_config, Field,\n                                          validator, ConfigError)\nfrom rastervision.pytorch_learner.learner_config import (\n    LearnerConfig, ModelConfig, Backbone, ImageDataConfig, GeoDataConfig,\n    GeoDataWindowMethod, GeoDataWindowConfig)\nfrom rastervision.pytorch_learner.dataset import (\n    ObjectDetectionImageDataset, ObjectDetectionSlidingWindowGeoDataset,\n    ObjectDetectionRandomWindowGeoDataset)\nfrom rastervision.pytorch_learner.utils import adjust_conv_channels\nfrom torchvision.models.detection.faster_rcnn import FasterRCNN\n\nif TYPE_CHECKING:\n    from rastervision.pytorch_learner.learner_config import SolverConfig\n\nlog = logging.getLogger(__name__)\n\nDEFAULT_BBOX_PARAMS = A.BboxParams(\n    format='albumentations', label_fields=['category_id'])\n\n\nclass ObjectDetectionDataFormat(Enum):\n    coco = 'coco'\n\n\ndef objdet_data_config_upgrader(cfg_dict, version):\n    if version == 1:\n        cfg_dict['type_hint'] = 'object_detection_image_data'\n    return cfg_dict\n\n\n@register_config('object_detection_data', upgrader=objdet_data_config_upgrader)\nclass ObjectDetectionDataConfig(Config):\n    def get_bbox_params(self):\n        return DEFAULT_BBOX_PARAMS\n\n\n@register_config('object_detection_image_data')\nclass ObjectDetectionImageDataConfig(ObjectDetectionDataConfig,\n                                     ImageDataConfig):\n    data_format: ObjectDetectionDataFormat = ObjectDetectionDataFormat.coco\n\n    def dir_to_dataset(self, data_dir: str, transform: A.BasicTransform\n                       ) -> ObjectDetectionImageDataset:\n        img_dir = join(data_dir, 'img')\n        annotation_uri = join(data_dir, 'labels.json')\n        ds = ObjectDetectionImageDataset(\n            img_dir, annotation_uri, transform=transform)\n        return ds\n\n\n@register_config('object_detection_geo_data_window')\nclass ObjectDetectionGeoDataWindowConfig(GeoDataWindowConfig):\n    ioa_thresh: float = Field(\n        0.8,\n        description='When a box is partially outside of a training chip, it '\n        'is not clear if (a clipped version) of the box should be included in '\n        'the chip. If the IOA (intersection over area) of the box with the '\n        'chip is greater than ioa_thresh, it is included in the chip. '\n        'Defaults to 0.8.')\n    clip: bool = Field(\n        False,\n        description='Clip bounding boxes to window limits when retrieving '\n        'labels for a window.')\n    neg_ratio: Optional[float] = Field(\n        None,\n        description='The ratio of negative chips (those containing no '\n        'bounding boxes) to positive chips. This can be useful if the '\n        'statistics of the background is different in positive chips. For '\n        'example, in car detection, the positive chips will always contain '\n        'roads, but no examples of rooftops since cars tend to not be near '\n        'rooftops. Defaults to None.')\n    neg_ioa_thresh: float = Field(\n        0.2,\n        description='A window will be considered negative if its max IoA with '\n        'any bounding box is less than this threshold. Defaults to 0.2.')\n\n\n@register_config('object_detection_geo_data')\nclass ObjectDetectionGeoDataConfig(ObjectDetectionDataConfig, GeoDataConfig):\n    def scene_to_dataset(\n            self,\n            scene: Scene,\n            transform: Optional[A.BasicTransform] = None,\n            bbox_params: Optional[A.BboxParams] = DEFAULT_BBOX_PARAMS\n    ) -> Union[ObjectDetectionSlidingWindowGeoDataset,\n               ObjectDetectionRandomWindowGeoDataset]:\n        if isinstance(self.window_opts, dict):\n            opts = self.window_opts[scene.id]\n        else:\n            opts = self.window_opts\n\n        if opts.method == GeoDataWindowMethod.sliding:\n            ds = ObjectDetectionSlidingWindowGeoDataset(\n                scene,\n                size=opts.size,\n                stride=opts.stride,\n                padding=opts.padding,\n                transform=transform)\n        elif opts.method == GeoDataWindowMethod.random:\n            ds = ObjectDetectionRandomWindowGeoDataset(\n                scene,\n                size_lims=opts.size_lims,\n                h_lims=opts.h_lims,\n                w_lims=opts.w_lims,\n                out_size=opts.size,\n                padding=opts.padding,\n                max_windows=opts.max_windows,\n                max_sample_attempts=opts.max_sample_attempts,\n                bbox_params=bbox_params,\n                ioa_thresh=opts.ioa_thresh,\n                clip=opts.clip,\n                neg_ratio=opts.neg_ratio,\n                neg_ioa_thresh=opts.neg_ioa_thresh,\n                efficient_aoi_sampling=opts.efficient_aoi_sampling,\n                transform=transform)\n        else:\n            raise NotImplementedError()\n        return ds\n\n\n@register_config('object_detection_model')\nclass ObjectDetectionModelConfig(ModelConfig):\n    backbone: Backbone = Field(\n        Backbone.resnet50,\n        description=\n        ('The torchvision.models backbone to use, which must be in the resnet* '\n         'family.'))\n\n    @validator('backbone')\n    def only_valid_backbones(cls, v):\n        if v not in [\n                Backbone.resnet18, Backbone.resnet34, Backbone.resnet50,\n                Backbone.resnet101, Backbone.resnet152\n        ]:\n            raise ValueError(\n                'The backbone for Faster-RCNN must be in the resnet* '\n                'family.')\n        return v\n\n    def build_default_model(self, num_classes: int, in_channels: int,\n                            img_sz: int) -> FasterRCNN:\n        \"\"\"Returns a FasterRCNN model.\n\n        Note that the model returned will have (num_classes + 2) output\n        classes. +1 for the null class (zeroth index), and another +1\n        (last index) for backward compatibility with earlier Raster Vision\n        versions.\n\n        Returns:\n            FasterRCNN: a FasterRCNN model.\n        \"\"\"\n        pretrained = self.pretrained\n        backbone_arch = self.get_backbone_str()\n        backbone = resnet_fpn_backbone(backbone_arch, pretrained)\n\n        # default values from FasterRCNN constructor\n        image_mean = [0.485, 0.456, 0.406]\n        image_std = [0.229, 0.224, 0.225]\n\n        if in_channels != 3:\n            extra_channels = in_channels - backbone.body['conv1'].in_channels\n\n            # adjust channels\n            backbone.body['conv1'] = adjust_conv_channels(\n                old_conv=backbone.body['conv1'],\n                in_channels=in_channels,\n                pretrained=pretrained)\n\n            # adjust stats\n            if extra_channels < 0:\n                image_mean = image_mean[:extra_channels]\n                image_std = image_std[:extra_channels]\n            else:\n                # arbitrarily set mean and stds of the new channels to\n                # something similar to the values of the other 3 channels\n                image_mean = image_mean + [.45] * extra_channels\n                image_std = image_std + [.225] * extra_channels\n\n        model = FasterRCNN(\n            backbone=backbone,\n            # +1 because torchvision detection models reserve 0 for the null\n            # class, another +1 for backward compatibility with earlier Raster\n            # Vision versions\n            num_classes=num_classes + 1 + 1,\n            # TODO we shouldn't need to pass the image size here\n            min_size=img_sz,\n            max_size=img_sz,\n            image_mean=image_mean,\n            image_std=image_std)\n        return model\n\n\n@register_config('object_detection_learner')\nclass ObjectDetectionLearnerConfig(LearnerConfig):\n    data: Union[ObjectDetectionImageDataConfig, ObjectDetectionGeoDataConfig]\n    model: Optional[ObjectDetectionModelConfig]\n\n    def build(self,\n              tmp_dir=None,\n              model_weights_path=None,\n              model_def_path=None,\n              loss_def_path=None,\n              training=True):\n        from rastervision.pytorch_learner.object_detection_learner import (\n            ObjectDetectionLearner)\n        return ObjectDetectionLearner(\n            self,\n            tmp_dir=tmp_dir,\n            model_weights_path=model_weights_path,\n            model_def_path=model_def_path,\n            loss_def_path=loss_def_path,\n            training=training)\n\n    @validator('solver')\n    def validate_solver_config(cls, v: 'SolverConfig') -> 'SolverConfig':\n        if v.ignore_class_index is not None:\n            raise ConfigError(\n                'ignore_last_class is not supported for Object Detection.')\n        if v.class_loss_weights is not None:\n            raise ConfigError(\n                'class_loss_weights is currently not supported for '\n                'Object Detection.')\n        if v.external_loss_def is not None:\n            raise ConfigError(\n                'external_loss_def is currently not supported for '\n                'Object Detection. Raster Vision expects object '\n                'detection models to behave like TorchVision object detection '\n                'models, and these models compute losses internally. So, if '\n                'you want to use a custom loss function, you can create a '\n                'custom model that implements that loss function and use that '\n                'model via external_model_def. See cowc_potsdam.py for an '\n                'example of how to use a custom object detection model.')\n        return v\n", "path": "rastervision_pytorch_learner/rastervision/pytorch_learner/object_detection_learner_config.py"}]}