prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Multiple issues with write_geotiff() method
  ### Expected behaviour
  I should be able to write a small GeoTiff file using the `datacube.helpers.write_geotiff()` method.
  
  I should also not have to specify a `time_index`.
  
  
  ### Actual behaviour
  
   For a 50x50 raster it fails with:
  ```python
      ValueError: blockxsize exceeds raster width
  ```
  
  And if `time_index` is not specified it fails with:
  ```pytb
  Exception ignored in: 'pandas._libs.lib.is_bool_array'
  TypeError: len() of unsized object
  Traceback (most recent call last):
    File "testlanwei.py", line 30, in <module>
      write_geotiff(filename='H8_test_1.tif', dataset=data, profile_override={'blockxsize':16, 'blockysize':16})
    File "/home/547/dra547/github/datacube-core/datacube/helpers.py", line 48, in write_geotiff
      dest.write(data.isel(time=time_index).data, bandnum)
    File "/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/dataarray.py", line 679, in isel
      ds = self._to_temp_dataset().isel(drop=drop, **indexers)
    File "/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/dataset.py", line 1143, in isel
      new_var = var.isel(**var_indexers)
    File "/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/variable.py", line 570, in isel
      return self[tuple(key)]
    File "/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/variable.py", line 1225, in __getitem__
      values = self._indexable_data[key]
    File "/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/indexing.py", line 545, in __getitem__
      result = self.array[key]
    File "/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/pandas/core/indexes/datetimelike.py", line 274, in __getitem__
      if com.is_bool_indexer(key):
    File "/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/pandas/core/common.py", line 189, in is_bool_indexer
      raise ValueError('cannot index with vector containing '
  ValueError: cannot index with vector containing NA / NaN values
  ```
  
  
  The function should be changed to take more useful parameters, and should have unit test coverage. 
  
  
  ### Steps to reproduce the behaviour
  
  Run the following script [broken_geotiff_write.txt](https://github.com/opendatacube/datacube-core/files/1204141/broken_geotiff_write.txt).
  
  
  
  ### Environment information
  
  * Which ``datacube --version`` are you using?
  `1.5.1`
  * What datacube deployment/enviornment are you running against?
  `agdc-py3-prod/1.5.1`
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `datacube/helpers.py`
  Content:
  ```
  1 """
  2 Useful functions for Datacube users
  3 
  4 Not used internally, those should go in `utils.py`
  5 """
  6 from __future__ import absolute_import
  7 import rasterio
  8 import numpy as np
  9 
  10 DEFAULT_PROFILE = {
  11     'blockxsize': 256,
  12     'blockysize': 256,
  13     'compress': 'lzw',
  14     'driver': 'GTiff',
  15     'interleave': 'band',
  16     'nodata': 0.0,
  17     'photometric': 'RGBA',
  18     'tiled': True}
  19 
  20 
  21 def write_geotiff(filename, dataset, time_index=None, profile_override=None):
  22     """
  23     Write an xarray dataset to a geotiff
  24 
  25     :param filename: Output filename
  26     :attr dataset: xarray dataset containing multiple bands to write to file
  27     :attr time_index: time index to write to file
  28     :attr profile_override: option dict, overrides rasterio file creation options.
  29     """
  30     profile_override = profile_override or {}
  31 
  32     try:
  33         dtypes = {val.dtype for val in dataset.data_vars.values()}
  34         assert len(dtypes) == 1  # Check for multiple dtypes
  35     except AttributeError:
  36         dtypes = [dataset.dtype]
  37 
  38     profile = DEFAULT_PROFILE.copy()
  39     profile.update({
  40         'width': dataset.dims[dataset.crs.dimensions[1]],
  41         'height': dataset.dims[dataset.crs.dimensions[0]],
  42         'affine': dataset.affine,
  43         'crs': dataset.crs.crs_str,
  44         'count': len(dataset.data_vars),
  45         'dtype': str(dtypes.pop())
  46     })
  47     profile.update(profile_override)
  48 
  49     with rasterio.open(str(filename), 'w', **profile) as dest:
  50         if hasattr(dataset, 'data_vars'):
  51             for bandnum, data in enumerate(dataset.data_vars.values(), start=1):
  52                 dest.write(data.isel(time=time_index).data, bandnum)
  53         else:  # Assume that we have a DataArray
  54             dest.write(dataset.isel(time=time_index).data, 0)
  55 
  56 
  57 def ga_pq_fuser(dest, src):
  58     """
  59     Fuse two Geoscience Australia Pixel Quality ndarrays
  60 
  61     To be used as a `fuse_func` when loaded `grouped` data, for example when grouping
  62     by solar day to avoid duplicate data from scene overlaps.
  63     """
  64     valid_bit = 8
  65     valid_val = (1 << valid_bit)
  66 
  67     no_data_dest_mask = ~(dest & valid_val).astype(bool)
  68     np.copyto(dest, src, where=no_data_dest_mask)
  69 
  70     both_data_mask = (valid_val & dest & src).astype(bool)
  71     np.copyto(dest, src & dest, where=both_data_mask)
  72
  ```
  --- 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/datacube/helpers.py b/datacube/helpers.py
  --- a/datacube/helpers.py
  +++ b/datacube/helpers.py
  @@ -4,8 +4,9 @@
   Not used internally, those should go in `utils.py`
   """
   from __future__ import absolute_import
  -import rasterio
  +
   import numpy as np
  +import rasterio
   
   DEFAULT_PROFILE = {
       'blockxsize': 256,
  @@ -18,17 +19,22 @@
       'tiled': True}
   
   
  -def write_geotiff(filename, dataset, time_index=None, profile_override=None):
  +def write_geotiff(filename, dataset, profile_override=None, time_index=None):
       """
  -    Write an xarray dataset to a geotiff
  +    Write an ODC style xarray.Dataset to a GeoTIFF file.
   
       :param filename: Output filename
  -    :attr dataset: xarray dataset containing multiple bands to write to file
  -    :attr time_index: time index to write to file
  -    :attr profile_override: option dict, overrides rasterio file creation options.
  +    :param dataset: xarray dataset containing one or more bands to write to a file.
  +    :param profile_override: option dict, overrides rasterio file creation options.
  +    :param time_index: DEPRECATED
       """
       profile_override = profile_override or {}
   
  +    if time_index is not None:
  +        raise ValueError('''The write_geotiff function no longer supports passing in `time_index`.
  +        The same function can be achieved by calling `dataset.isel(time=<time_index>)` before passing
  +        in your dataset. It was removed because it made the function much less useful for more advanced cases.''')
  +
       try:
           dtypes = {val.dtype for val in dataset.data_vars.values()}
           assert len(dtypes) == 1  # Check for multiple dtypes
  @@ -39,19 +45,35 @@
       profile.update({
           'width': dataset.dims[dataset.crs.dimensions[1]],
           'height': dataset.dims[dataset.crs.dimensions[0]],
  -        'affine': dataset.affine,
  +        'transform': dataset.affine,
           'crs': dataset.crs.crs_str,
           'count': len(dataset.data_vars),
           'dtype': str(dtypes.pop())
       })
       profile.update(profile_override)
   
  +    _calculate_blocksize(profile)
  +
       with rasterio.open(str(filename), 'w', **profile) as dest:
           if hasattr(dataset, 'data_vars'):
               for bandnum, data in enumerate(dataset.data_vars.values(), start=1):
  -                dest.write(data.isel(time=time_index).data, bandnum)
  -        else:  # Assume that we have a DataArray
  -            dest.write(dataset.isel(time=time_index).data, 0)
  +                dest.write(data.data, bandnum)
  +
  +
  +def _calculate_blocksize(profile):
  +    # Block size must be smaller than the image size, and for geotiffs must be divisible by 16
  +    # Fix for small images.
  +    if profile['blockxsize'] > profile['width']:
  +        if profile['width'] % 16 == 0 or profile['width'] < 16:
  +            profile['blockxsize'] = profile['width']
  +        else:
  +            profile['blockxsize'] = 16
  +
  +    if profile['blockysize'] > profile['height']:
  +        if profile['height'] % 16 == 0 or profile['height'] < 16:
  +            profile['blockysize'] = profile['height']
  +        else:
  +            profile['blockysize'] = 16
   
   
   def ga_pq_fuser(dest, src):
  
verification_info: {"golden_diff": "diff --git a/datacube/helpers.py b/datacube/helpers.py\n--- a/datacube/helpers.py\n+++ b/datacube/helpers.py\n@@ -4,8 +4,9 @@\n Not used internally, those should go in `utils.py`\n \"\"\"\n from __future__ import absolute_import\n-import rasterio\n+\n import numpy as np\n+import rasterio\n \n DEFAULT_PROFILE = {\n     'blockxsize': 256,\n@@ -18,17 +19,22 @@\n     'tiled': True}\n \n \n-def write_geotiff(filename, dataset, time_index=None, profile_override=None):\n+def write_geotiff(filename, dataset, profile_override=None, time_index=None):\n     \"\"\"\n-    Write an xarray dataset to a geotiff\n+    Write an ODC style xarray.Dataset to a GeoTIFF file.\n \n     :param filename: Output filename\n-    :attr dataset: xarray dataset containing multiple bands to write to file\n-    :attr time_index: time index to write to file\n-    :attr profile_override: option dict, overrides rasterio file creation options.\n+    :param dataset: xarray dataset containing one or more bands to write to a file.\n+    :param profile_override: option dict, overrides rasterio file creation options.\n+    :param time_index: DEPRECATED\n     \"\"\"\n     profile_override = profile_override or {}\n \n+    if time_index is not None:\n+        raise ValueError('''The write_geotiff function no longer supports passing in `time_index`.\n+        The same function can be achieved by calling `dataset.isel(time=<time_index>)` before passing\n+        in your dataset. It was removed because it made the function much less useful for more advanced cases.''')\n+\n     try:\n         dtypes = {val.dtype for val in dataset.data_vars.values()}\n         assert len(dtypes) == 1  # Check for multiple dtypes\n@@ -39,19 +45,35 @@\n     profile.update({\n         'width': dataset.dims[dataset.crs.dimensions[1]],\n         'height': dataset.dims[dataset.crs.dimensions[0]],\n-        'affine': dataset.affine,\n+        'transform': dataset.affine,\n         'crs': dataset.crs.crs_str,\n         'count': len(dataset.data_vars),\n         'dtype': str(dtypes.pop())\n     })\n     profile.update(profile_override)\n \n+    _calculate_blocksize(profile)\n+\n     with rasterio.open(str(filename), 'w', **profile) as dest:\n         if hasattr(dataset, 'data_vars'):\n             for bandnum, data in enumerate(dataset.data_vars.values(), start=1):\n-                dest.write(data.isel(time=time_index).data, bandnum)\n-        else:  # Assume that we have a DataArray\n-            dest.write(dataset.isel(time=time_index).data, 0)\n+                dest.write(data.data, bandnum)\n+\n+\n+def _calculate_blocksize(profile):\n+    # Block size must be smaller than the image size, and for geotiffs must be divisible by 16\n+    # Fix for small images.\n+    if profile['blockxsize'] > profile['width']:\n+        if profile['width'] % 16 == 0 or profile['width'] < 16:\n+            profile['blockxsize'] = profile['width']\n+        else:\n+            profile['blockxsize'] = 16\n+\n+    if profile['blockysize'] > profile['height']:\n+        if profile['height'] % 16 == 0 or profile['height'] < 16:\n+            profile['blockysize'] = profile['height']\n+        else:\n+            profile['blockysize'] = 16\n \n \n def ga_pq_fuser(dest, src):\n", "issue": "Multiple issues with write_geotiff() method\n### Expected behaviour\r\nI should be able to write a small GeoTiff file using the `datacube.helpers.write_geotiff()` method.\r\n\r\nI should also not have to specify a `time_index`.\r\n\r\n\r\n### Actual behaviour\r\n\r\n For a 50x50 raster it fails with:\r\n```python\r\n    ValueError: blockxsize exceeds raster width\r\n```\r\n\r\nAnd if `time_index` is not specified it fails with:\r\n```pytb\r\nException ignored in: 'pandas._libs.lib.is_bool_array'\r\nTypeError: len() of unsized object\r\nTraceback (most recent call last):\r\n  File \"testlanwei.py\", line 30, in <module>\r\n    write_geotiff(filename='H8_test_1.tif', dataset=data, profile_override={'blockxsize':16, 'blockysize':16})\r\n  File \"/home/547/dra547/github/datacube-core/datacube/helpers.py\", line 48, in write_geotiff\r\n    dest.write(data.isel(time=time_index).data, bandnum)\r\n  File \"/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/dataarray.py\", line 679, in isel\r\n    ds = self._to_temp_dataset().isel(drop=drop, **indexers)\r\n  File \"/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/dataset.py\", line 1143, in isel\r\n    new_var = var.isel(**var_indexers)\r\n  File \"/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/variable.py\", line 570, in isel\r\n    return self[tuple(key)]\r\n  File \"/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/variable.py\", line 1225, in __getitem__\r\n    values = self._indexable_data[key]\r\n  File \"/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/xarray/core/indexing.py\", line 545, in __getitem__\r\n    result = self.array[key]\r\n  File \"/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/pandas/core/indexes/datetimelike.py\", line 274, in __getitem__\r\n    if com.is_bool_indexer(key):\r\n  File \"/g/data/v10/public/modules/agdc-py3-env/20170713/envs/agdc/lib/python3.6/site-packages/pandas/core/common.py\", line 189, in is_bool_indexer\r\n    raise ValueError('cannot index with vector containing '\r\nValueError: cannot index with vector containing NA / NaN values\r\n```\r\n\r\n\r\nThe function should be changed to take more useful parameters, and should have unit test coverage. \r\n\r\n\r\n### Steps to reproduce the behaviour\r\n\r\nRun the following script [broken_geotiff_write.txt](https://github.com/opendatacube/datacube-core/files/1204141/broken_geotiff_write.txt).\r\n\r\n\r\n\r\n### Environment information\r\n\r\n* Which ``datacube --version`` are you using?\r\n`1.5.1`\r\n* What datacube deployment/enviornment are you running against?\r\n`agdc-py3-prod/1.5.1`\n", "before_files": [{"content": "\"\"\"\nUseful functions for Datacube users\n\nNot used internally, those should go in `utils.py`\n\"\"\"\nfrom __future__ import absolute_import\nimport rasterio\nimport numpy as np\n\nDEFAULT_PROFILE = {\n    'blockxsize': 256,\n    'blockysize': 256,\n    'compress': 'lzw',\n    'driver': 'GTiff',\n    'interleave': 'band',\n    'nodata': 0.0,\n    'photometric': 'RGBA',\n    'tiled': True}\n\n\ndef write_geotiff(filename, dataset, time_index=None, profile_override=None):\n    \"\"\"\n    Write an xarray dataset to a geotiff\n\n    :param filename: Output filename\n    :attr dataset: xarray dataset containing multiple bands to write to file\n    :attr time_index: time index to write to file\n    :attr profile_override: option dict, overrides rasterio file creation options.\n    \"\"\"\n    profile_override = profile_override or {}\n\n    try:\n        dtypes = {val.dtype for val in dataset.data_vars.values()}\n        assert len(dtypes) == 1  # Check for multiple dtypes\n    except AttributeError:\n        dtypes = [dataset.dtype]\n\n    profile = DEFAULT_PROFILE.copy()\n    profile.update({\n        'width': dataset.dims[dataset.crs.dimensions[1]],\n        'height': dataset.dims[dataset.crs.dimensions[0]],\n        'affine': dataset.affine,\n        'crs': dataset.crs.crs_str,\n        'count': len(dataset.data_vars),\n        'dtype': str(dtypes.pop())\n    })\n    profile.update(profile_override)\n\n    with rasterio.open(str(filename), 'w', **profile) as dest:\n        if hasattr(dataset, 'data_vars'):\n            for bandnum, data in enumerate(dataset.data_vars.values(), start=1):\n                dest.write(data.isel(time=time_index).data, bandnum)\n        else:  # Assume that we have a DataArray\n            dest.write(dataset.isel(time=time_index).data, 0)\n\n\ndef ga_pq_fuser(dest, src):\n    \"\"\"\n    Fuse two Geoscience Australia Pixel Quality ndarrays\n\n    To be used as a `fuse_func` when loaded `grouped` data, for example when grouping\n    by solar day to avoid duplicate data from scene overlaps.\n    \"\"\"\n    valid_bit = 8\n    valid_val = (1 << valid_bit)\n\n    no_data_dest_mask = ~(dest & valid_val).astype(bool)\n    np.copyto(dest, src, where=no_data_dest_mask)\n\n    both_data_mask = (valid_val & dest & src).astype(bool)\n    np.copyto(dest, src & dest, where=both_data_mask)\n", "path": "datacube/helpers.py"}], "after_files": [{"content": "\"\"\"\nUseful functions for Datacube users\n\nNot used internally, those should go in `utils.py`\n\"\"\"\nfrom __future__ import absolute_import\n\nimport numpy as np\nimport rasterio\n\nDEFAULT_PROFILE = {\n    'blockxsize': 256,\n    'blockysize': 256,\n    'compress': 'lzw',\n    'driver': 'GTiff',\n    'interleave': 'band',\n    'nodata': 0.0,\n    'photometric': 'RGBA',\n    'tiled': True}\n\n\ndef write_geotiff(filename, dataset, profile_override=None, time_index=None):\n    \"\"\"\n    Write an ODC style xarray.Dataset to a GeoTIFF file.\n\n    :param filename: Output filename\n    :param dataset: xarray dataset containing one or more bands to write to a file.\n    :param profile_override: option dict, overrides rasterio file creation options.\n    :param time_index: DEPRECATED\n    \"\"\"\n    profile_override = profile_override or {}\n\n    if time_index is not None:\n        raise ValueError('''The write_geotiff function no longer supports passing in `time_index`.\n        The same function can be achieved by calling `dataset.isel(time=<time_index>)` before passing\n        in your dataset. It was removed because it made the function much less useful for more advanced cases.''')\n\n    try:\n        dtypes = {val.dtype for val in dataset.data_vars.values()}\n        assert len(dtypes) == 1  # Check for multiple dtypes\n    except AttributeError:\n        dtypes = [dataset.dtype]\n\n    profile = DEFAULT_PROFILE.copy()\n    profile.update({\n        'width': dataset.dims[dataset.crs.dimensions[1]],\n        'height': dataset.dims[dataset.crs.dimensions[0]],\n        'transform': dataset.affine,\n        'crs': dataset.crs.crs_str,\n        'count': len(dataset.data_vars),\n        'dtype': str(dtypes.pop())\n    })\n    profile.update(profile_override)\n\n    _calculate_blocksize(profile)\n\n    with rasterio.open(str(filename), 'w', **profile) as dest:\n        if hasattr(dataset, 'data_vars'):\n            for bandnum, data in enumerate(dataset.data_vars.values(), start=1):\n                dest.write(data.data, bandnum)\n\n\ndef _calculate_blocksize(profile):\n    # Block size must be smaller than the image size, and for geotiffs must be divisible by 16\n    # Fix for small images.\n    if profile['blockxsize'] > profile['width']:\n        if profile['width'] % 16 == 0 or profile['width'] < 16:\n            profile['blockxsize'] = profile['width']\n        else:\n            profile['blockxsize'] = 16\n\n    if profile['blockysize'] > profile['height']:\n        if profile['height'] % 16 == 0 or profile['height'] < 16:\n            profile['blockysize'] = profile['height']\n        else:\n            profile['blockysize'] = 16\n\n\ndef ga_pq_fuser(dest, src):\n    \"\"\"\n    Fuse two Geoscience Australia Pixel Quality ndarrays\n\n    To be used as a `fuse_func` when loaded `grouped` data, for example when grouping\n    by solar day to avoid duplicate data from scene overlaps.\n    \"\"\"\n    valid_bit = 8\n    valid_val = (1 << valid_bit)\n\n    no_data_dest_mask = ~(dest & valid_val).astype(bool)\n    np.copyto(dest, src, where=no_data_dest_mask)\n\n    both_data_mask = (valid_val & dest & src).astype(bool)\n    np.copyto(dest, src & dest, where=both_data_mask)\n", "path": "datacube/helpers.py"}]}