Repository: googleapis/google-cloud-python

Issue:
Vision: if one of RGB values for dominant color is absent, should default to 0

If you can provide us with as much of the following information as possible it
will help us identify the cause of your issue more quickly.

1.  OS type and version

     macOS Sierra 10.12.1

2.  Python version and virtual environment information `python --version`

    ```
    $ python --version
    Python 2.7.12
    ```

3.  google-cloud-python version `pip show google-cloud`, `pip show google-<service>` or `pip freeze`

    ```
    $ pip freeze | grep google
    google-cloud-core==0.21.0
    google-cloud-vision==0.21.0
    googleapis-common-protos==1.5.0
    ```
4.  Stacktrace if available

    ```
    Traceback (most recent call last):
      File "getpalette.py", line 108, in <module>
        main(args.images)
      File "getpalette.py", line 97, in main
        pixelfy(image_path)
      File "getpalette.py", line 58, in pixelfy
        properties = image.detect_properties()
      File "/Users/swast/venvs/cloud-vision/lib/python2.7/site-packages/google/cloud/vision/image.py", line 189, in detect_properties
        return self._detect_annotation(features)
      File "/Users/swast/venvs/cloud-vision/lib/python2.7/site-packages/google/cloud/vision/image.py", line 125, in _detect_annotation
        _entity_from_response_type(feature.feature_type, results))
      File "/Users/swast/venvs/cloud-vision/lib/python2.7/site-packages/google/cloud/vision/image.py", line 229, in _entity_from_response_type
        ImagePropertiesAnnotation.from_api_repr(results[feature_key]))
      File "/Users/swast/venvs/cloud-vision/lib/python2.7/site-packages/google/cloud/vision/color.py", line 40, in from_api_repr
        response['dominantColors']['colors']]
      File "/Users/swast/venvs/cloud-vision/lib/python2.7/site-packages/google/cloud/vision/color.py", line 160, in from_api_repr
        color = Color.from_api_repr(response['color'])
      File "/Users/swast/venvs/cloud-vision/lib/python2.7/site-packages/google/cloud/vision/color.py", line 90, in from_api_repr
        blue = response['blue']
    KeyError: 'blue'
    ```

5.  Steps to reproduce

    Run the code example against the following image. `python getpalette.py cat.png`

    ![image](https://cloud.githubusercontent.com/assets/247555/20451516/1ac543c4-adaf-11e6-847a-a0913ca2fe12.png)

    Photo by [Chen YiZhou](https://stock.tookapic.com/photos/73879)

6. Code example `getpalette.py`

```
# Copyright 2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import division
from __future__ import print_function

import argparse
import io
import os
import os.path

from google.cloud import vision
from PIL import Image


def resize(size, max_dimension=500):
    width, height = size
    if width < height:
        return (int((width / height) * max_dimension), max_dimension)
    return (max_dimension, int((height / width) * max_dimension))


def pixelfy(image_path):
    print(image_path)
    vision_client = vision.Client()
    im = Image.open(image_path)
    im = im.resize(resize(im.size), resample=Image.LANCZOS)
    f = io.BytesIO()
    im.save(f, format='jpeg')
    image = vision_client.image(content=f.getvalue())
    properties = image.detect_properties()
    palette = []

    for property_ in properties:
        for color in property_.colors:
            palette.append(
                (
                    color.color.red,
                    color.color.green,
                    color.color.blue,
                ))
    print(palette)


def main(image_paths):
    for image_path in image_paths:
        pixelfy(image_path)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('images', help='Path to image(s) to get palette.', nargs='+')

    args = parser.parse_args()

    main(args.images)
```


Discussion:
So this is pretty interesting. Apparently there's no blue in that image.
Based on the documentation, I was under the assumption that the values would be there but just set to `0`. Which would be the right thing to do upstream.

@omaray @dhermes I think this is something that should change upstream. I could fix it for this case but not returning the default values seems wrong.

https://github.com/googleapis/googleapis/blob/acc991c2dd8e86a7299548b0bef7b7c5c1374eb0/google/cloud/vision/v1/image_annotator.proto#L389
@daspecster I don't think this can change upstream. I've filed similar bugs for the natural language API, which were closed as working as intended.

The reason this is happening is that internally the API converts a protocol buffer into JSON. According to the [documentation on JSON-mapping](https://developers.google.com/protocol-buffers/docs/proto3#json) "If a field has the default value in the protocol buffer, it will be omitted in the JSON-encoded data by default to save space".
Thanks for the explaination @tswast!

In that case, I'll try and audit vision and try and catch other places this happens as well.

Resolving patch:
diff --git a/vision/google/cloud/vision/color.py b/vision/google/cloud/vision/color.py
--- a/vision/google/cloud/vision/color.py
+++ b/vision/google/cloud/vision/color.py
@@ -36,8 +36,9 @@ def from_api_repr(cls, response):
         :rtype: :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
         :returns: Populated instance of ``ImagePropertiesAnnotation``.
         """
-        colors = [ColorInformation.from_api_repr(color) for color in
-                  response['dominantColors']['colors']]
+        raw_colors = response.get('dominantColors', {}).get('colors', ())
+        colors = [ColorInformation.from_api_repr(color)
+                  for color in raw_colors]
         return cls(colors)
 
     @property
@@ -85,10 +86,10 @@ def from_api_repr(cls, response):
         :rtype: :class:`~google.cloud.vision.color.Color`
         :returns: Instance of :class:`~google.cloud.vision.color.Color`.
         """
-        red = response['red']
-        green = response['green']
-        blue = response['blue']
-        alpha = response.get('alpha')
+        red = response.get('red', 0)
+        green = response.get('green', 0)
+        blue = response.get('blue', 0)
+        alpha = response.get('alpha', 0.0)
 
         return cls(red, green, blue, alpha)
 
@@ -157,9 +158,9 @@ def from_api_repr(cls, response):
         :rtype: :class:`~google.cloud.vision.color.ColorInformation`
         :returns: Instance of ``ColorInformation``.
         """
-        color = Color.from_api_repr(response['color'])
-        score = response['score']
-        pixel_fraction = response['pixelFraction']
+        color = Color.from_api_repr(response.get('color'))
+        score = response.get('score')
+        pixel_fraction = response.get('pixelFraction')
 
         return cls(color, score, pixel_fraction)
 


Test patch:
diff --git a/vision/unit_tests/test_color.py b/vision/unit_tests/test_color.py
new file mode 100644
--- /dev/null
+++ b/vision/unit_tests/test_color.py
@@ -0,0 +1,47 @@
+# Copyright 2016 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+
+class TestColor(unittest.TestCase):
+    @staticmethod
+    def _get_target_class():
+        from google.cloud.vision.color import Color
+        return Color
+
+    def test_rgb_color_data(self):
+        colors = {
+            'red': 255,
+            'green': 255,
+            'blue': 255,
+            'alpha': 0.5,
+        }
+        color_class = self._get_target_class()
+        colors = color_class.from_api_repr(colors)
+
+        self.assertEqual(colors.red, 255)
+        self.assertEqual(colors.green, 255)
+        self.assertEqual(colors.blue, 255)
+        self.assertEqual(colors.alpha, 0.5)
+
+    def test_missing_rgb_values(self):
+        colors = {}
+        color_class = self._get_target_class()
+        colors = color_class.from_api_repr(colors)
+
+        self.assertEqual(colors.red, 0)
+        self.assertEqual(colors.green, 0)
+        self.assertEqual(colors.blue, 0)
+        self.assertEqual(colors.alpha, 0.0)