prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Fix raster stats bug
  If you run compute_raster_stats on 4-channel imagery (yielding stats for 4 channels), and use a `channel_order` of [0, 1, 2] in your raster_transformer, and then switch to using 3-channel imagery, it leads to an error because currently the `means` do not have the `channel_order` applied to them before being subtracted from the raster. In other words, 4 channel means is subtracted from a 3 channel raster.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/rastervision/builders/raster_transformer_builder.py`
  Content:
  ```
  1 from rastervision.core.raster_transformer import RasterTransformer
  2 
  3 
  4 def build(config):
  5     return RasterTransformer(config)
  6
  ```
  
  Path: `src/rastervision/core/raster_transformer.py`
  Content:
  ```
  1 import numpy as np
  2 
  3 from rastervision.core.raster_stats import RasterStats
  4 
  5 
  6 class RasterTransformer(object):
  7     """Transforms chips according to a config."""
  8 
  9     def __init__(self, options):
  10         """Construct a new RasterTransformer.
  11 
  12         Args:
  13             options: protos.raster_transformer_pb2.RasterTransformer
  14         """
  15         self.options = options
  16         self.raster_stats = None
  17         if options.stats_uri:
  18             self.raster_stats = RasterStats()
  19             self.raster_stats.load(options.stats_uri)
  20 
  21     def transform(self, chip):
  22         """Transform a chip.
  23 
  24         Selects a subset of the channels and transforms non-uint8 to
  25         uint8 values using options.stats_uri
  26 
  27         Args:
  28             chip: [height, width, channels] numpy array
  29 
  30         Returns:
  31             [height, width, channels] uint8 numpy array where channels is equal
  32                 to len(self.options.channel_order)
  33         """
  34         if chip.dtype != np.uint8:
  35             if self.raster_stats:
  36                 # Subtract mean and divide by std to get zscores.
  37                 means = np.array(self.raster_stats.means)
  38                 means = means[np.newaxis, np.newaxis, :].astype(np.float)
  39                 stds = np.array(self.raster_stats.stds)
  40                 stds = stds[np.newaxis, np.newaxis, :].astype(np.float)
  41 
  42                 # Don't transform NODATA zero values.
  43                 nodata = chip == 0
  44 
  45                 chip = chip - means
  46                 chip = chip / stds
  47 
  48                 # Make zscores that fall between -3 and 3 span 0 to 255.
  49                 chip += 3
  50                 chip /= 6
  51 
  52                 chip = np.clip(chip, 0, 1)
  53                 chip *= 255
  54                 chip = chip.astype(np.uint8)
  55 
  56                 chip[nodata] = 0
  57             else:
  58                 raise ValueError(
  59                     'Need to provide stats_uri for non-uint8 rasters.')
  60 
  61         if self.options.channel_order:
  62             return chip[:, :, self.options.channel_order]
  63         return chip
  64
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/src/rastervision/builders/raster_transformer_builder.py b/src/rastervision/builders/raster_transformer_builder.py
  --- a/src/rastervision/builders/raster_transformer_builder.py
  +++ b/src/rastervision/builders/raster_transformer_builder.py
  @@ -1,5 +1,12 @@
   from rastervision.core.raster_transformer import RasterTransformer
  +from rastervision.core.raster_stats import RasterStats
   
   
   def build(config):
  -    return RasterTransformer(config)
  +    raster_stats = None
  +    if config.stats_uri:
  +        raster_stats = RasterStats()
  +        raster_stats.load(config.stats_uri)
  +
  +    return RasterTransformer(
  +        channel_order=config.channel_order, raster_stats=raster_stats)
  diff --git a/src/rastervision/core/raster_transformer.py b/src/rastervision/core/raster_transformer.py
  --- a/src/rastervision/core/raster_transformer.py
  +++ b/src/rastervision/core/raster_transformer.py
  @@ -1,43 +1,50 @@
   import numpy as np
   
  -from rastervision.core.raster_stats import RasterStats
  -
   
   class RasterTransformer(object):
  -    """Transforms chips according to a config."""
  +    """Transforms raw chips to be input to a neural network."""
   
  -    def __init__(self, options):
  +    def __init__(self, channel_order=None, raster_stats=None):
           """Construct a new RasterTransformer.
   
           Args:
  -            options: protos.raster_transformer_pb2.RasterTransformer
  +            channel_order: numpy array of length n where n is the number of
  +                channels to use and the values are channel indices
  +            raster_stats: (RasterStats) used to transform chip to have
  +                desired statistics
           """
  -        self.options = options
  -        self.raster_stats = None
  -        if options.stats_uri:
  -            self.raster_stats = RasterStats()
  -            self.raster_stats.load(options.stats_uri)
  +        self.channel_order = channel_order
  +        self.raster_stats = raster_stats
   
       def transform(self, chip):
           """Transform a chip.
   
           Selects a subset of the channels and transforms non-uint8 to
  -        uint8 values using options.stats_uri
  +        uint8 values using raster_stats.
   
           Args:
               chip: [height, width, channels] numpy array
   
           Returns:
               [height, width, channels] uint8 numpy array where channels is equal
  -                to len(self.options.channel_order)
  +                to len(channel_order)
           """
  +        if self.channel_order is None:
  +            channel_order = np.arange(chip.shape[2])
  +        else:
  +            channel_order = self.channel_order
  +
  +        chip = chip[:, :, channel_order]
  +
           if chip.dtype != np.uint8:
               if self.raster_stats:
                   # Subtract mean and divide by std to get zscores.
                   means = np.array(self.raster_stats.means)
  -                means = means[np.newaxis, np.newaxis, :].astype(np.float)
  +                means = means[np.newaxis, np.newaxis, channel_order].astype(
  +                    np.float)
                   stds = np.array(self.raster_stats.stds)
  -                stds = stds[np.newaxis, np.newaxis, :].astype(np.float)
  +                stds = stds[np.newaxis, np.newaxis, channel_order].astype(
  +                    np.float)
   
                   # Don't transform NODATA zero values.
                   nodata = chip == 0
  @@ -56,8 +63,6 @@
                   chip[nodata] = 0
               else:
                   raise ValueError(
  -                    'Need to provide stats_uri for non-uint8 rasters.')
  +                    'Need to provide raster_stats for non-uint8 rasters.')
   
  -        if self.options.channel_order:
  -            return chip[:, :, self.options.channel_order]
           return chip
  
verification_info: {"golden_diff": "diff --git a/src/rastervision/builders/raster_transformer_builder.py b/src/rastervision/builders/raster_transformer_builder.py\n--- a/src/rastervision/builders/raster_transformer_builder.py\n+++ b/src/rastervision/builders/raster_transformer_builder.py\n@@ -1,5 +1,12 @@\n from rastervision.core.raster_transformer import RasterTransformer\n+from rastervision.core.raster_stats import RasterStats\n \n \n def build(config):\n-    return RasterTransformer(config)\n+    raster_stats = None\n+    if config.stats_uri:\n+        raster_stats = RasterStats()\n+        raster_stats.load(config.stats_uri)\n+\n+    return RasterTransformer(\n+        channel_order=config.channel_order, raster_stats=raster_stats)\ndiff --git a/src/rastervision/core/raster_transformer.py b/src/rastervision/core/raster_transformer.py\n--- a/src/rastervision/core/raster_transformer.py\n+++ b/src/rastervision/core/raster_transformer.py\n@@ -1,43 +1,50 @@\n import numpy as np\n \n-from rastervision.core.raster_stats import RasterStats\n-\n \n class RasterTransformer(object):\n-    \"\"\"Transforms chips according to a config.\"\"\"\n+    \"\"\"Transforms raw chips to be input to a neural network.\"\"\"\n \n-    def __init__(self, options):\n+    def __init__(self, channel_order=None, raster_stats=None):\n         \"\"\"Construct a new RasterTransformer.\n \n         Args:\n-            options: protos.raster_transformer_pb2.RasterTransformer\n+            channel_order: numpy array of length n where n is the number of\n+                channels to use and the values are channel indices\n+            raster_stats: (RasterStats) used to transform chip to have\n+                desired statistics\n         \"\"\"\n-        self.options = options\n-        self.raster_stats = None\n-        if options.stats_uri:\n-            self.raster_stats = RasterStats()\n-            self.raster_stats.load(options.stats_uri)\n+        self.channel_order = channel_order\n+        self.raster_stats = raster_stats\n \n     def transform(self, chip):\n         \"\"\"Transform a chip.\n \n         Selects a subset of the channels and transforms non-uint8 to\n-        uint8 values using options.stats_uri\n+        uint8 values using raster_stats.\n \n         Args:\n             chip: [height, width, channels] numpy array\n \n         Returns:\n             [height, width, channels] uint8 numpy array where channels is equal\n-                to len(self.options.channel_order)\n+                to len(channel_order)\n         \"\"\"\n+        if self.channel_order is None:\n+            channel_order = np.arange(chip.shape[2])\n+        else:\n+            channel_order = self.channel_order\n+\n+        chip = chip[:, :, channel_order]\n+\n         if chip.dtype != np.uint8:\n             if self.raster_stats:\n                 # Subtract mean and divide by std to get zscores.\n                 means = np.array(self.raster_stats.means)\n-                means = means[np.newaxis, np.newaxis, :].astype(np.float)\n+                means = means[np.newaxis, np.newaxis, channel_order].astype(\n+                    np.float)\n                 stds = np.array(self.raster_stats.stds)\n-                stds = stds[np.newaxis, np.newaxis, :].astype(np.float)\n+                stds = stds[np.newaxis, np.newaxis, channel_order].astype(\n+                    np.float)\n \n                 # Don't transform NODATA zero values.\n                 nodata = chip == 0\n@@ -56,8 +63,6 @@\n                 chip[nodata] = 0\n             else:\n                 raise ValueError(\n-                    'Need to provide stats_uri for non-uint8 rasters.')\n+                    'Need to provide raster_stats for non-uint8 rasters.')\n \n-        if self.options.channel_order:\n-            return chip[:, :, self.options.channel_order]\n         return chip\n", "issue": "Fix raster stats bug\nIf you run compute_raster_stats on 4-channel imagery (yielding stats for 4 channels), and use a `channel_order` of [0, 1, 2] in your raster_transformer, and then switch to using 3-channel imagery, it leads to an error because currently the `means` do not have the `channel_order` applied to them before being subtracted from the raster. In other words, 4 channel means is subtracted from a 3 channel raster.\n", "before_files": [{"content": "from rastervision.core.raster_transformer import RasterTransformer\n\n\ndef build(config):\n    return RasterTransformer(config)\n", "path": "src/rastervision/builders/raster_transformer_builder.py"}, {"content": "import numpy as np\n\nfrom rastervision.core.raster_stats import RasterStats\n\n\nclass RasterTransformer(object):\n    \"\"\"Transforms chips according to a config.\"\"\"\n\n    def __init__(self, options):\n        \"\"\"Construct a new RasterTransformer.\n\n        Args:\n            options: protos.raster_transformer_pb2.RasterTransformer\n        \"\"\"\n        self.options = options\n        self.raster_stats = None\n        if options.stats_uri:\n            self.raster_stats = RasterStats()\n            self.raster_stats.load(options.stats_uri)\n\n    def transform(self, chip):\n        \"\"\"Transform a chip.\n\n        Selects a subset of the channels and transforms non-uint8 to\n        uint8 values using options.stats_uri\n\n        Args:\n            chip: [height, width, channels] numpy array\n\n        Returns:\n            [height, width, channels] uint8 numpy array where channels is equal\n                to len(self.options.channel_order)\n        \"\"\"\n        if chip.dtype != np.uint8:\n            if self.raster_stats:\n                # Subtract mean and divide by std to get zscores.\n                means = np.array(self.raster_stats.means)\n                means = means[np.newaxis, np.newaxis, :].astype(np.float)\n                stds = np.array(self.raster_stats.stds)\n                stds = stds[np.newaxis, np.newaxis, :].astype(np.float)\n\n                # Don't transform NODATA zero values.\n                nodata = chip == 0\n\n                chip = chip - means\n                chip = chip / stds\n\n                # Make zscores that fall between -3 and 3 span 0 to 255.\n                chip += 3\n                chip /= 6\n\n                chip = np.clip(chip, 0, 1)\n                chip *= 255\n                chip = chip.astype(np.uint8)\n\n                chip[nodata] = 0\n            else:\n                raise ValueError(\n                    'Need to provide stats_uri for non-uint8 rasters.')\n\n        if self.options.channel_order:\n            return chip[:, :, self.options.channel_order]\n        return chip\n", "path": "src/rastervision/core/raster_transformer.py"}], "after_files": [{"content": "from rastervision.core.raster_transformer import RasterTransformer\nfrom rastervision.core.raster_stats import RasterStats\n\n\ndef build(config):\n    raster_stats = None\n    if config.stats_uri:\n        raster_stats = RasterStats()\n        raster_stats.load(config.stats_uri)\n\n    return RasterTransformer(\n        channel_order=config.channel_order, raster_stats=raster_stats)\n", "path": "src/rastervision/builders/raster_transformer_builder.py"}, {"content": "import numpy as np\n\n\nclass RasterTransformer(object):\n    \"\"\"Transforms raw chips to be input to a neural network.\"\"\"\n\n    def __init__(self, channel_order=None, raster_stats=None):\n        \"\"\"Construct a new RasterTransformer.\n\n        Args:\n            channel_order: numpy array of length n where n is the number of\n                channels to use and the values are channel indices\n            raster_stats: (RasterStats) used to transform chip to have\n                desired statistics\n        \"\"\"\n        self.channel_order = channel_order\n        self.raster_stats = raster_stats\n\n    def transform(self, chip):\n        \"\"\"Transform a chip.\n\n        Selects a subset of the channels and transforms non-uint8 to\n        uint8 values using raster_stats.\n\n        Args:\n            chip: [height, width, channels] numpy array\n\n        Returns:\n            [height, width, channels] uint8 numpy array where channels is equal\n                to len(channel_order)\n        \"\"\"\n        if self.channel_order is None:\n            channel_order = np.arange(chip.shape[2])\n        else:\n            channel_order = self.channel_order\n\n        chip = chip[:, :, channel_order]\n\n        if chip.dtype != np.uint8:\n            if self.raster_stats:\n                # Subtract mean and divide by std to get zscores.\n                means = np.array(self.raster_stats.means)\n                means = means[np.newaxis, np.newaxis, channel_order].astype(\n                    np.float)\n                stds = np.array(self.raster_stats.stds)\n                stds = stds[np.newaxis, np.newaxis, channel_order].astype(\n                    np.float)\n\n                # Don't transform NODATA zero values.\n                nodata = chip == 0\n\n                chip = chip - means\n                chip = chip / stds\n\n                # Make zscores that fall between -3 and 3 span 0 to 255.\n                chip += 3\n                chip /= 6\n\n                chip = np.clip(chip, 0, 1)\n                chip *= 255\n                chip = chip.astype(np.uint8)\n\n                chip[nodata] = 0\n            else:\n                raise ValueError(\n                    'Need to provide raster_stats for non-uint8 rasters.')\n\n        return chip\n", "path": "src/rastervision/core/raster_transformer.py"}]}