text: import datetime as dt
  
  from unittest import SkipTest, skipIf
  
  import numpy as np
  import pytest
  
  from holoviews import (
      Dimension, Curve, Points, Image, Dataset, RGB, Path, Graph, TriMesh,
      QuadMesh, NdOverlay, Contours, Spikes, Spread, Area, Rectangles,
      Segments, Polygons, Nodes
  )
  from holoviews.streams import Tap
  from holoviews.element.comparison import ComparisonTestCase
  from numpy import nan
  
  try:
      import datashader as ds
      import dask.dataframe as dd
      import xarray as xr
      from holoviews.core.util import pd
      from holoviews.operation.datashader import (
          LooseVersion, aggregate, regrid, ds_version, stack, directly_connect_edges,
          shade, spread, rasterize, datashade, AggregationOperation,
          inspect, inspect_points, inspect_polygons
      )
  except:
      raise SkipTest('Datashader not available')
  
  try:
      import cudf
      import cupy
  except:
      cudf = None
  
  try:
      import spatialpandas
  except:
      spatialpandas = None
  
  spatialpandas_skip = skipIf(spatialpandas is None, "SpatialPandas not available")
  cudf_skip = skipIf(cudf is None, "cuDF not available")
  
  
  import logging
  
  numba_logger = logging.getLogger('numba')
  numba_logger.setLevel(logging.WARNING)
  
  AggregationOperation.vdim_prefix = ''
  
  class DatashaderAggregateTests(ComparisonTestCase):
      """
      Tests for datashader aggregation
      """
  
      def test_aggregate_points(self):
          points = Points([(0.2, 0.3), (0.4, 0.7), (0, 0.99)])
          img = aggregate(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 0]]),
                           vdims=[Dimension('Count', nodata=0)])
          self.assertEqual(img, expected)
  
      def test_aggregate_points_count_column(self):
          points = Points([(0.2, 0.3, np.NaN), (0.4, 0.7, 22), (0, 0.99,np.NaN)], vdims='z')
          img = aggregate(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2, aggregator=ds.count('z'))
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[0, 0], [1, 0]]),
                           vdims=[Dimension('z Count', nodata=0)])
          self.assertEqual(img, expected)
  
      @cudf_skip
      def test_aggregate_points_cudf(self):
          points = Points([(0.2, 0.3), (0.4, 0.7), (0, 0.99)], datatype=['cuDF'])
          self.assertIsInstance(points.data, cudf.DataFrame)
          img = aggregate(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 0]]),
                           vdims=[Dimension('Count', nodata=0)])
          self.assertIsInstance(img.data.Count.data, cupy.ndarray)
          self.assertEqual(img, expected)
  
      def test_aggregate_zero_range_points(self):
          p = Points([(0, 0), (1, 1)])
          agg = rasterize(p, x_range=(0, 0), y_range=(0, 1), expand=False, dynamic=False,
                          width=2, height=2)
          img = Image(([], [0.25, 0.75], np.zeros((2, 0))), bounds=(0, 0, 0, 1),
                      xdensity=1, vdims=[Dimension('Count', nodata=0)])
          self.assertEqual(agg, img)
  
      def test_aggregate_points_target(self):
          points = Points([(0.2, 0.3), (0.4, 0.7), (0, 0.99)])
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 0]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = aggregate(points, dynamic=False,  target=expected)
          self.assertEqual(img, expected)
  
      def test_aggregate_points_sampling(self):
          points = Points([(0.2, 0.3), (0.4, 0.7), (0, 0.99)])
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 0]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = aggregate(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          x_sampling=0.5, y_sampling=0.5)
          self.assertEqual(img, expected)
  
      def test_aggregate_points_categorical(self):
          points = Points([(0.2, 0.3, 'A'), (0.4, 0.7, 'B'), (0, 0.99, 'C')], vdims='z')
          img = aggregate(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2, aggregator=ds.count_cat('z'))
          xs, ys = [0.25, 0.75], [0.25, 0.75]
          expected = NdOverlay({'A': Image((xs, ys, [[1, 0], [0, 0]]), vdims=Dimension('z Count', nodata=0)),
                                'B': Image((xs, ys, [[0, 0], [1, 0]]), vdims=Dimension('z Count', nodata=0)),
                                'C': Image((xs, ys, [[0, 0], [1, 0]]), vdims=Dimension('z Count', nodata=0))},
                               kdims=['z'])
          self.assertEqual(img, expected)
  
      def test_aggregate_points_categorical_zero_range(self):
          points = Points([(0.2, 0.3, 'A'), (0.4, 0.7, 'B'), (0, 0.99, 'C')], vdims='z')
          img = aggregate(points, dynamic=False,  x_range=(0, 0), y_range=(0, 1),
                          aggregator=ds.count_cat('z'), height=2)
          xs, ys = [], [0.25, 0.75]
          params = dict(bounds=(0, 0, 0, 1), xdensity=1)
          expected = NdOverlay({'A': Image((xs, ys, np.zeros((2, 0))), vdims=Dimension('z Count', nodata=0), **params),
                                'B': Image((xs, ys, np.zeros((2, 0))), vdims=Dimension('z Count', nodata=0), **params),
                                'C': Image((xs, ys, np.zeros((2, 0))), vdims=Dimension('z Count', nodata=0), **params)},
                               kdims=['z'])
          self.assertEqual(img, expected)
  
      def test_aggregate_curve(self):
          curve = Curve([(0.2, 0.3), (0.4, 0.7), (0.8, 0.99)])
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [1, 1]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = aggregate(curve, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          self.assertEqual(img, expected)
  
      def test_aggregate_curve_datetimes(self):
          dates = pd.date_range(start="2016-01-01", end="2016-01-03", freq='1D')
          curve = Curve((dates, [1, 2, 3]))
          img = aggregate(curve, width=2, height=2, dynamic=False)
          bounds = (np.datetime64('2016-01-01T00:00:00.000000'), 1.0,
                    np.datetime64('2016-01-03T00:00:00.000000'), 3.0)
          dates = [np.datetime64('2016-01-01T12:00:00.000000000'),
                   np.datetime64('2016-01-02T12:00:00.000000000')]
          expected = Image((dates, [1.5, 2.5], [[1, 0], [0, 2]]),
                           datatype=['xarray'], bounds=bounds, vdims=Dimension('Count', nodata=0))
          self.assertEqual(img, expected)
  
      def test_aggregate_curve_datetimes_dask(self):
          df = pd.DataFrame(
              data=np.arange(1000), columns=['a'],
              index=pd.date_range('2019-01-01', freq='1T', periods=1000),
          )
          ddf = dd.from_pandas(df, npartitions=4)
          curve = Curve(ddf, kdims=['index'], vdims=['a'])
          img = aggregate(curve, width=2, height=3, dynamic=False)
          bounds = (np.datetime64('2019-01-01T00:00:00.000000'), 0.0,
                    np.datetime64('2019-01-01T16:39:00.000000'), 999.0)
          dates = [np.datetime64('2019-01-01T04:09:45.000000000'),
                   np.datetime64('2019-01-01T12:29:15.000000000')]
          expected = Image((dates, [166.5, 499.5, 832.5], [[332, 0], [167, 166], [0, 334]]),
                           kdims=['index', 'a'], vdims=Dimension('Count', nodata=0),
                           datatype=['xarray'], bounds=bounds)
          self.assertEqual(img, expected)
  
      def test_aggregate_curve_datetimes_microsecond_timebase(self):
          dates = pd.date_range(start="2016-01-01", end="2016-01-03", freq='1D')
          xstart = np.datetime64('2015-12-31T23:59:59.723518000', 'us')
          xend = np.datetime64('2016-01-03T00:00:00.276482000', 'us')
          curve = Curve((dates, [1, 2, 3]))
          img = aggregate(curve, width=2, height=2, x_range=(xstart, xend), dynamic=False)
          bounds = (np.datetime64('2015-12-31T23:59:59.723518'), 1.0,
                    np.datetime64('2016-01-03T00:00:00.276482'), 3.0)
          dates = [np.datetime64('2016-01-01T11:59:59.861759000',),
                   np.datetime64('2016-01-02T12:00:00.138241000')]
          expected = Image((dates, [1.5, 2.5], [[1, 0], [0, 2]]),
                           datatype=['xarray'], bounds=bounds, vdims=Dimension('Count', nodata=0))
          self.assertEqual(img, expected)
  
      def test_aggregate_ndoverlay_count_cat_datetimes_microsecond_timebase(self):
          dates = pd.date_range(start="2016-01-01", end="2016-01-03", freq='1D')
          xstart = np.datetime64('2015-12-31T23:59:59.723518000', 'us')
          xend = np.datetime64('2016-01-03T00:00:00.276482000', 'us')
          curve = Curve((dates, [1, 2, 3]))
          curve2 = Curve((dates, [3, 2, 1]))
          ndoverlay = NdOverlay({0: curve, 1: curve2}, 'Cat')
          imgs = aggregate(ndoverlay, aggregator=ds.count_cat('Cat'), width=2, height=2,
                           x_range=(xstart, xend), dynamic=False)
          bounds = (np.datetime64('2015-12-31T23:59:59.723518'), 1.0,
                    np.datetime64('2016-01-03T00:00:00.276482'), 3.0)
          dates = [np.datetime64('2016-01-01T11:59:59.861759000',),
                   np.datetime64('2016-01-02T12:00:00.138241000')]
          expected = Image((dates, [1.5, 2.5], [[1, 0], [0, 2]]),
                           datatype=['xarray'], bounds=bounds, vdims=Dimension('Count', nodata=0))
          expected2 = Image((dates, [1.5, 2.5], [[0, 1], [1, 1]]),
                           datatype=['xarray'], bounds=bounds, vdims=Dimension('Count', nodata=0))
          self.assertEqual(imgs[0], expected)
          self.assertEqual(imgs[1], expected2)
  
      def test_aggregate_dt_xaxis_constant_yaxis(self):
          df = pd.DataFrame({'y': np.ones(100)}, index=pd.date_range('1980-01-01', periods=100, freq='1T'))
          img = rasterize(Curve(df), dynamic=False, width=3)
          xs = np.array(['1980-01-01T00:16:30.000000', '1980-01-01T00:49:30.000000',
                         '1980-01-01T01:22:30.000000'], dtype='datetime64[us]')
          ys = np.array([])
          bounds = (np.datetime64('1980-01-01T00:00:00.000000'), 1.0,
                    np.datetime64('1980-01-01T01:39:00.000000'), 1.0)
          expected = Image((xs, ys, np.empty((0, 3))), ['index', 'y'],
                           vdims=Dimension('Count', nodata=0), xdensity=1,
                           ydensity=1, bounds=bounds)
          self.assertEqual(img, expected)
  
      def test_aggregate_ndoverlay(self):
          ds = Dataset([(0.2, 0.3, 0), (0.4, 0.7, 1), (0, 0.99, 2)], kdims=['x', 'y', 'z'])
          ndoverlay = ds.to(Points, ['x', 'y'], [], 'z').overlay()
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 0]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = aggregate(ndoverlay, dynamic=False, x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          self.assertEqual(img, expected)
  
      def test_aggregate_path(self):
          path = Path([[(0.2, 0.3), (0.4, 0.7)], [(0.4, 0.7), (0.8, 0.99)]])
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 1]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = aggregate(path, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          self.assertEqual(img, expected)
  
      def test_aggregate_contours_with_vdim(self):
          contours = Contours([[(0.2, 0.3, 1), (0.4, 0.7, 1)], [(0.4, 0.7, 2), (0.8, 0.99, 2)]], vdims='z')
          img = rasterize(contours, dynamic=False)
          self.assertEqual(img.vdims, ['z'])
  
      def test_aggregate_contours_without_vdim(self):
          contours = Contours([[(0.2, 0.3), (0.4, 0.7)], [(0.4, 0.7), (0.8, 0.99)]])
          img = rasterize(contours, dynamic=False)
          self.assertEqual(img.vdims, [Dimension('Any', nodata=0)])
  
      def test_aggregate_dframe_nan_path(self):
          path = Path([Path([[(0.2, 0.3), (0.4, 0.7)], [(0.4, 0.7), (0.8, 0.99)]]).dframe()])
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 1]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = aggregate(path, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          self.assertEqual(img, expected)
  
      def test_spikes_aggregate_count(self):
          spikes = Spikes([1, 2, 3])
          agg = rasterize(spikes, width=5, dynamic=False, expand=False)
          expected = Image(np.array([[1, 0, 1, 0, 1]]), vdims=Dimension('Count', nodata=0),
                           xdensity=2.5, ydensity=1, bounds=(1, 0, 3, 0.5))
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_count_dask(self):
          spikes = Spikes([1, 2, 3], datatype=['dask'])
          agg = rasterize(spikes, width=5, dynamic=False, expand=False)
          expected = Image(np.array([[1, 0, 1, 0, 1]]), vdims=Dimension('Count', nodata=0),
                           xdensity=2.5, ydensity=1, bounds=(1, 0, 3, 0.5))
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_dt_count(self):
          spikes = Spikes([dt.datetime(2016, 1, 1),  dt.datetime(2016, 1, 2), dt.datetime(2016, 1, 3)])
          agg = rasterize(spikes, width=5, dynamic=False, expand=False)
          bounds = (np.datetime64('2016-01-01T00:00:00.000000'), 0,
                    np.datetime64('2016-01-03T00:00:00.000000'), 0.5)
          expected = Image(np.array([[1, 0, 1, 0, 1]]), vdims=Dimension('Count', nodata=0), bounds=bounds)
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_dt_count_dask(self):
          spikes = Spikes([dt.datetime(2016, 1, 1),  dt.datetime(2016, 1, 2), dt.datetime(2016, 1, 3)],
                          datatype=['dask'])
          agg = rasterize(spikes, width=5, dynamic=False, expand=False)
          bounds = (np.datetime64('2016-01-01T00:00:00.000000'), 0,
                    np.datetime64('2016-01-03T00:00:00.000000'), 0.5)
          expected = Image(np.array([[1, 0, 1, 0, 1]]), vdims=Dimension('Count', nodata=0), bounds=bounds)
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_spike_length(self):
          spikes = Spikes([1, 2, 3])
          agg = rasterize(spikes, width=5, dynamic=False, expand=False, spike_length=7)
          expected = Image(np.array([[1, 0, 1, 0, 1]]), vdims=Dimension('Count', nodata=0),
                           xdensity=2.5, ydensity=1, bounds=(1, 0, 3, 7.0))
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_with_height_count(self):
          spikes = Spikes([(1, 0.2), (2, 0.8), (3, 0.4)], vdims='y')
          agg = rasterize(spikes, width=5, height=5, y_range=(0, 1), dynamic=False)
          xs = [1.2, 1.6, 2.0, 2.4, 2.8]
          ys = [0.1, 0.3, 0.5, 0.7, 0.9]
          arr = np.array([
              [1, 0, 1, 0, 1],
              [1, 0, 1, 0, 1],
              [0, 0, 1, 0, 1],
              [0, 0, 1, 0, 0],
              [0, 0, 1, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_with_height_count_override(self):
          spikes = Spikes([(1, 0.2), (2, 0.8), (3, 0.4)], vdims='y')
          agg = rasterize(spikes, width=5, height=5, y_range=(0, 1),
                          spike_length=0.3, dynamic=False)
          xs = [1.2, 1.6, 2.0, 2.4, 2.8]
          ys = [0.1, 0.3, 0.5, 0.7, 0.9]
          arr = np.array([[1, 0, 1, 0, 1],
                          [1, 0, 1, 0, 1],
                          [0, 0, 0, 0, 0],
                          [0, 0, 0, 0, 0],
                          [0, 0, 0, 0, 0]])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_rasterize_regrid_and_spikes_overlay(self):
          img = Image(([0.5, 1.5], [0.5, 1.5], [[0, 1], [2, 3]]))
          spikes = Spikes([(0.5, 0.2), (1.5, 0.8), ], vdims='y')
  
          expected_regrid = Image(([0.25, 0.75, 1.25, 1.75],
                                   [0.25, 0.75, 1.25, 1.75],
                                   [[0, 0, 1, 1],
                                    [0, 0, 1, 1],
                                    [2, 2, 3, 3],
                                    [2, 2, 3, 3]]))
          spikes_arr = np.array([[0, 1, 0, 1],
                                 [0, 1, 0, 1],
                                 [0, 0, 0, 0],
                                 [0, 0, 0, 0]])
          expected_spikes = Image(([0.25, 0.75, 1.25, 1.75],
                                   [0.25, 0.75, 1.25, 1.75], spikes_arr), vdims=Dimension('Count', nodata=0))
          overlay = img * spikes
          agg = rasterize(overlay, width=4, height=4, x_range=(0, 2), y_range=(0, 2),
                          spike_length=0.5, upsample=True, dynamic=False)
          self.assertEqual(agg.Image.I, expected_regrid)
          self.assertEqual(agg.Spikes.I, expected_spikes)
  
  
      def test_spikes_aggregate_with_height_count_dask(self):
          spikes = Spikes([(1, 0.2), (2, 0.8), (3, 0.4)], vdims='y', datatype=['dask'])
          agg = rasterize(spikes, width=5, height=5, y_range=(0, 1), dynamic=False)
          xs = [1.2, 1.6, 2.0, 2.4, 2.8]
          ys = [0.1, 0.3, 0.5, 0.7, 0.9]
          arr = np.array([
              [1, 0, 1, 0, 1],
              [1, 0, 1, 0, 1],
              [0, 0, 1, 0, 1],
              [0, 0, 1, 0, 0],
              [0, 0, 1, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_with_negative_height_count(self):
          spikes = Spikes([(1, -0.2), (2, -0.8), (3, -0.4)], vdims='y', datatype=['dask'])
          agg = rasterize(spikes, width=5, height=5, y_range=(-1, 0), dynamic=False)
          xs = [1.2, 1.6, 2.0, 2.4, 2.8]
          ys = [-0.9, -0.7, -0.5, -0.3, -0.1]
          arr = np.array([
              [0, 0, 0, 0, 0],
              [0, 0, 1, 0, 0],
              [0, 0, 1, 0, 0],
              [0, 0, 1, 0, 1],
              [1, 0, 1, 0, 1]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_spikes_aggregate_with_positive_and_negative_height_count(self):
          spikes = Spikes([(1, -0.2), (2, 0.8), (3, -0.4)], vdims='y', datatype=['dask'])
          agg = rasterize(spikes, width=5, height=5, y_range=(-1, 1), dynamic=False)
          xs = [1.2, 1.6, 2.0, 2.4, 2.8]
          ys = [-0.8, -0.4, 0.0, 0.4, 0.8]
          arr = np.array([
              [0, 0, 0, 0, 0],
              [0, 0, 0, 0, 1],
              [1, 0, 1, 0, 1],
              [0, 0, 1, 0, 0],
              [0, 0, 1, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_rectangles_aggregate_count(self):
          rects = Rectangles([(0, 0, 1, 2), (1, 1, 3, 2)])
          agg = rasterize(rects, width=4, height=4, dynamic=False)
          xs = [0.375, 1.125, 1.875, 2.625]
          ys = [0.25, 0.75, 1.25, 1.75]
          arr = np.array([
              [1, 1, 0, 0],
              [1, 1, 0, 0],
              [1, 2, 1, 1],
              [0, 0, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_rectangles_aggregate_count_cat(self):
          rects = Rectangles([(0, 0, 1, 2, 'A'), (1, 1, 3, 2, 'B')], vdims=['cat'])
          agg = rasterize(rects, width=4, height=4, aggregator=ds.count_cat('cat'),
                          dynamic=False)
          xs = [0.375, 1.125, 1.875, 2.625]
          ys = [0.25, 0.75, 1.25, 1.75]
          arr1 = np.array([
              [1, 1, 0, 0],
              [1, 1, 0, 0],
              [1, 1, 0, 0],
              [0, 0, 0, 0]
          ])
          arr2 = np.array([
              [0, 0, 0, 0],
              [0, 0, 0, 0],
              [0, 1, 1, 1],
              [0, 0, 0, 0]
          ])
          expected1 = Image((xs, ys, arr1), vdims=Dimension('cat Count', nodata=0))
          expected2 = Image((xs, ys, arr2), vdims=Dimension('cat Count', nodata=0))
          expected = NdOverlay({'A': expected1, 'B': expected2}, kdims=['cat'])
          self.assertEqual(agg, expected)
  
      def test_rectangles_aggregate_sum(self):
          rects = Rectangles([(0, 0, 1, 2, 0.5), (1, 1, 3, 2, 1.5)], vdims=['value'])
          agg = rasterize(rects, width=4, height=4, aggregator='sum', dynamic=False)
          xs = [0.375, 1.125, 1.875, 2.625]
          ys = [0.25, 0.75, 1.25, 1.75]
          nan = np.nan
          arr = np.array([
              [0.5, 0.5, nan, nan],
              [0.5, 0.5, nan, nan],
              [0.5, 2. , 1.5, 1.5],
              [nan, nan, nan, nan]
          ])
          expected = Image((xs, ys, arr), vdims='value')
          self.assertEqual(agg, expected)
  
      def test_rectangles_aggregate_dt_count(self):
          rects = Rectangles([
              (0, dt.datetime(2016, 1, 2), 4, dt.datetime(2016, 1, 3)),
              (1, dt.datetime(2016, 1, 1), 2, dt.datetime(2016, 1, 5))
          ])
          agg = rasterize(rects, width=4, height=4, dynamic=False)
          xs = [0.5, 1.5, 2.5, 3.5]
          ys = [
              np.datetime64('2016-01-01T12:00:00'), np.datetime64('2016-01-02T12:00:00'),
              np.datetime64('2016-01-03T12:00:00'), np.datetime64('2016-01-04T12:00:00')
          ]
          arr = np.array([
              [0, 1, 1, 0],
              [1, 2, 2, 1],
              [0, 1, 1, 0],
              [0, 0, 0, 0]
          ])
          bounds = (0.0, np.datetime64('2016-01-01T00:00:00'),
                    4.0, np.datetime64('2016-01-05T00:00:00'))
          expected = Image((xs, ys, arr), bounds=bounds, vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_segments_aggregate_count(self):
          segments = Segments([(0, 1, 4, 1), (1, 0, 1, 4)])
          agg = rasterize(segments, width=4, height=4, dynamic=False)
          xs = [0.5, 1.5, 2.5, 3.5]
          ys = [0.5, 1.5, 2.5, 3.5]
          arr = np.array([
              [0, 1, 0, 0],
              [1, 2, 1, 1],
              [0, 1, 0, 0],
              [0, 1, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_segments_aggregate_sum(self, instance=False):
          segments = Segments([(0, 1, 4, 1, 2), (1, 0, 1, 4, 4)], vdims=['value'])
          if instance:
              agg = rasterize.instance(
                  width=10, height=10, dynamic=False, aggregator='sum'
              )(segments, width=4, height=4)
          else:
              agg = rasterize(
                  segments, width=4, height=4, dynamic=False, aggregator='sum'
              )
          xs = [0.5, 1.5, 2.5, 3.5]
          ys = [0.5, 1.5, 2.5, 3.5]
          na = np.nan
          arr = np.array([
              [na, 4, na, na],
              [2 , 6, 2 , 2 ],
              [na, 4, na, na],
              [na, 4, na, na]
          ])
          expected = Image((xs, ys, arr), vdims='value')
          self.assertEqual(agg, expected)
  
      def test_segments_aggregate_sum_instance(self):
          self.test_segments_aggregate_sum(instance=True)
  
      def test_segments_aggregate_dt_count(self):
          segments = Segments([
              (0, dt.datetime(2016, 1, 2), 4, dt.datetime(2016, 1, 2)),
              (1, dt.datetime(2016, 1, 1), 1, dt.datetime(2016, 1, 5))
          ])
          agg = rasterize(segments, width=4, height=4, dynamic=False)
          xs = [0.5, 1.5, 2.5, 3.5]
          ys = [
              np.datetime64('2016-01-01T12:00:00'), np.datetime64('2016-01-02T12:00:00'),
              np.datetime64('2016-01-03T12:00:00'), np.datetime64('2016-01-04T12:00:00')
          ]
          arr = np.array([
              [0, 1, 0, 0],
              [1, 2, 1, 1],
              [0, 1, 0, 0],
              [0, 1, 0, 0]
          ])
          bounds = (0.0, np.datetime64('2016-01-01T00:00:00'),
                    4.0, np.datetime64('2016-01-05T00:00:00'))
          expected = Image((xs, ys, arr), bounds=bounds, vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_area_aggregate_simple_count(self):
          area = Area([1, 2, 1])
          agg = rasterize(area, width=4, height=4, y_range=(0, 3), dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.375, 1.125, 1.875, 2.625]
          arr = np.array([
              [1, 1, 1, 1],
              [1, 1, 1, 1],
              [0, 1, 1, 0],
              [0, 0, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_area_aggregate_negative_count(self):
          area = Area([-1, -2, -3])
          agg = rasterize(area, width=4, height=4, y_range=(-3, 0), dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [-2.625, -1.875, -1.125, -0.375]
          arr = np.array([
              [0, 0, 0, 1],
              [0, 1, 1, 1],
              [1, 1, 1, 1],
              [1, 1, 1, 1]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_area_aggregate_crossover_count(self):
          area = Area([-1, 2, 3])
          agg = rasterize(area, width=4, height=4, y_range=(-3, 3), dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [-2.25, -0.75, 0.75, 2.25]
          arr = np.array([
              [0, 0, 0, 0],
              [1, 0, 0, 0],
              [1, 1, 1, 1],
              [0, 0, 1, 1]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_spread_aggregate_symmetric_count(self):
          spread = Spread([(0, 1, 0.8), (1, 2, 0.3), (2, 3, 0.8)])
          agg = rasterize(spread, width=4, height=4, dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.65, 1.55, 2.45, 3.35]
          arr = np.array([
              [0, 0, 0, 0],
              [1, 0, 0, 0],
              [0, 1, 1, 0],
              [0, 0, 0, 1]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_spread_aggregate_assymmetric_count(self):
          spread = Spread([(0, 1, 0.4, 0.8), (1, 2, 0.8, 0.4), (2, 3, 0.5, 1)],
                          vdims=['y', 'pos', 'neg'])
          agg = rasterize(spread, width=4, height=4, dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.6125, 1.4375, 2.2625, 3.0875]
          arr = np.array([
              [0, 0, 0, 0],
              [1, 0, 0, 0],
              [0, 1, 1, 0],
              [0, 0, 1, 1]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      def test_rgb_regrid_packed(self):
          coords = {'x': [1, 2], 'y': [1, 2], 'band': [0, 1, 2]}
          arr = np.array([
              [[255, 10],
               [  0, 30]],
              [[  1,  0],
               [  0,  0]],
              [[127,  0],
               [  0, 68]],
          ]).T
          da = xr.DataArray(data=arr, dims=('x', 'y', 'band'), coords=coords)
          im = RGB(da, ['x', 'y'])
          agg = rasterize(im, width=3, height=3, dynamic=False, upsample=True)
          xs = [0.8333333, 1.5, 2.166666]
          ys = [0.8333333, 1.5, 2.166666]
          arr = np.array([
              [[255, 255, 10],
               [255, 255, 10],
               [  0,   0, 30]],
              [[  1,   1,  0],
               [  1,   1,  0],
               [  0,   0,  0]],
              [[127, 127,  0],
               [127, 127,  0],
               [  0,   0, 68]],
          ]).transpose((1, 2, 0))
          expected = RGB((xs, ys, arr))
          self.assertEqual(agg, expected)
  
      @spatialpandas_skip
      def test_line_rasterize(self):
          path = Path([[(0, 0), (1, 1), (2, 0)], [(0, 0), (0, 1)]], datatype=['spatialpandas'])
          agg = rasterize(path, width=4, height=4, dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.125, 0.375, 0.625, 0.875]
          arr = np.array([
              [2, 0, 0, 1],
              [1, 1, 0, 1],
              [1, 1, 1, 0],
              [1, 0, 1, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      @spatialpandas_skip
      def test_multi_line_rasterize(self):
          path = Path([{'x': [0, 1, 2, np.nan, 0, 0], 'y': [0, 1, 0, np.nan, 0, 1]}],
                      datatype=['spatialpandas'])
          agg = rasterize(path, width=4, height=4, dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.125, 0.375, 0.625, 0.875]
          arr = np.array([
              [2, 0, 0, 1],
              [1, 1, 0, 1],
              [1, 1, 1, 0],
              [1, 0, 1, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      @spatialpandas_skip
      def test_ring_rasterize(self):
          path = Path([{'x': [0, 1, 2], 'y': [0, 1, 0], 'geom_type': 'Ring'}], datatype=['spatialpandas'])
          agg = rasterize(path, width=4, height=4, dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.125, 0.375, 0.625, 0.875]
          arr = np.array([
              [1, 1, 1, 1],
              [0, 1, 0, 1],
              [0, 1, 1, 0],
              [0, 0, 1, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      @spatialpandas_skip
      def test_polygon_rasterize(self):
          poly = Polygons([
              {'x': [0, 1, 2], 'y': [0, 1, 0],
               'holes': [[[(1.6, 0.2), (1, 0.8), (0.4, 0.2)]]]}
          ])
          agg = rasterize(poly, width=6, height=6, dynamic=False)
          xs = [0.166667, 0.5, 0.833333, 1.166667, 1.5, 1.833333]
          ys = [0.083333, 0.25, 0.416667, 0.583333, 0.75, 0.916667]
          arr = np.array([
              [1, 1, 1, 1, 1, 1],
              [0, 0, 0, 0, 0, 0],
              [0, 1, 0, 0, 1, 0],
              [0, 0, 0, 0, 0, 0],
              [0, 0, 1, 1, 0, 0],
              [0, 0, 0, 0, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
      @spatialpandas_skip
      def test_polygon_rasterize_mean_agg(self):
          poly = Polygons([
              {'x': [0, 1, 2], 'y': [0, 1, 0], 'z': 2.4},
              {'x': [0, 0, 1], 'y': [0, 1, 1], 'z': 3.6}
          ], vdims='z')
          agg = rasterize(poly, width=4, height=4, dynamic=False, aggregator='mean')
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.125, 0.375, 0.625, 0.875]
          arr = np.array([
              [ 2.4,  2.4,  2.4,    2.4],
              [ 3.6,  2.4,  2.4,    np.nan],
              [ 3.6,  2.4,  2.4,    np.nan],
              [ 3.6,  3.6,  np.nan, np.nan]])
          expected = Image((xs, ys, arr), vdims='z')
          self.assertEqual(agg, expected)
  
      @spatialpandas_skip
      def test_multi_poly_rasterize(self):
          poly = Polygons([{'x': [0, 1, 2, np.nan, 0, 0, 1],
                            'y': [0, 1, 0, np.nan, 0, 1, 1]}],
                          datatype=['spatialpandas'])
          agg = rasterize(poly, width=4, height=4, dynamic=False)
          xs = [0.25, 0.75, 1.25, 1.75]
          ys = [0.125, 0.375, 0.625, 0.875]
          arr = np.array([
              [1, 1, 1, 1],
              [1, 1, 1, 0],
              [1, 1, 1, 0],
              [1, 1, 0, 0]
          ])
          expected = Image((xs, ys, arr), vdims=Dimension('Count', nodata=0))
          self.assertEqual(agg, expected)
  
  
  
  class DatashaderCatAggregateTests(ComparisonTestCase):
  
      def setUp(self):
          if ds_version < LooseVersion('0.11.0'):
              raise SkipTest('Regridding operations require datashader>=0.11.0')
  
      def test_aggregate_points_categorical(self):
          points = Points([(0.2, 0.3, 'A'), (0.4, 0.7, 'B'), (0, 0.99, 'C')], vdims='z')
          img = aggregate(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2, aggregator=ds.by('z', ds.count()))
          xs, ys = [0.25, 0.75], [0.25, 0.75]
          expected = NdOverlay({'A': Image((xs, ys, [[1, 0], [0, 0]]), vdims=Dimension('z Count', nodata=0)),
                                'B': Image((xs, ys, [[0, 0], [1, 0]]), vdims=Dimension('z Count', nodata=0)),
                                'C': Image((xs, ys, [[0, 0], [1, 0]]), vdims=Dimension('z Count', nodata=0))},
                               kdims=['z'])
          self.assertEqual(img, expected)
  
  
      def test_aggregate_points_categorical_mean(self):
          points = Points([(0.2, 0.3, 'A', 0.1), (0.4, 0.7, 'B', 0.2), (0, 0.99, 'C', 0.3)], vdims=['cat', 'z'])
          img = aggregate(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2, aggregator=ds.by('cat', ds.mean('z')))
          xs, ys = [0.25, 0.75], [0.25, 0.75]
          expected = NdOverlay({'A': Image((xs, ys, [[0.1, nan], [nan, nan]]), vdims='z'),
                                'B': Image((xs, ys, [[nan, nan], [0.2, nan]]), vdims='z'),
                                'C': Image((xs, ys, [[nan, nan], [0.3, nan]]), vdims='z')},
                               kdims=['cat'])
          self.assertEqual(img, expected)
  
  
  
  class DatashaderShadeTests(ComparisonTestCase):
  
      def test_shade_categorical_images_xarray(self):
          xs, ys = [0.25, 0.75], [0.25, 0.75]
          data = NdOverlay({'A': Image((xs, ys, np.array([[1, 0], [0, 0]], dtype='u4')),
                                       datatype=['xarray'], vdims=Dimension('z Count', nodata=0)),
                            'B': Image((xs, ys, np.array([[0, 0], [1, 0]], dtype='u4')),
                                       datatype=['xarray'], vdims=Dimension('z Count', nodata=0)),
                            'C': Image((xs, ys, np.array([[0, 0], [1, 0]], dtype='u4')),
                                       datatype=['xarray'], vdims=Dimension('z Count', nodata=0))},
                           kdims=['z'])
          shaded = shade(data)
          r = [[228, 120], [66, 120]]
          g = [[26, 109], [150, 109]]
          b = [[28, 95], [129, 95]]
          a = [[40, 0], [255, 0]]
          expected = RGB((xs, ys, r, g, b, a), datatype=['grid'],
                         vdims=RGB.vdims+[Dimension('A', range=(0, 1))])
          self.assertEqual(shaded, expected)
  
      def test_shade_categorical_images_grid(self):
          xs, ys = [0.25, 0.75], [0.25, 0.75]
          data = NdOverlay({'A': Image((xs, ys, np.array([[1, 0], [0, 0]], dtype='u4')),
                                       datatype=['grid'], vdims=Dimension('z Count', nodata=0)),
                            'B': Image((xs, ys, np.array([[0, 0], [1, 0]], dtype='u4')),
                                       datatype=['grid'], vdims=Dimension('z Count', nodata=0)),
                            'C': Image((xs, ys, np.array([[0, 0], [1, 0]], dtype='u4')),
                                       datatype=['grid'], vdims=Dimension('z Count', nodata=0))},
                           kdims=['z'])
          shaded = shade(data)
          r = [[228, 120], [66, 120]]
          g = [[26, 109], [150, 109]]
          b = [[28, 95], [129, 95]]
          a = [[40, 0], [255, 0]]
          expected = RGB((xs, ys, r, g, b, a), datatype=['grid'],
                         vdims=RGB.vdims+[Dimension('A', range=(0, 1))])
          self.assertEqual(shaded, expected)
  
      def test_shade_dt_xaxis_constant_yaxis(self):
          df = pd.DataFrame({'y': np.ones(100)}, index=pd.date_range('1980-01-01', periods=100, freq='1T'))
          rgb = shade(rasterize(Curve(df), dynamic=False, width=3))
          xs = np.array(['1980-01-01T00:16:30.000000', '1980-01-01T00:49:30.000000',
                         '1980-01-01T01:22:30.000000'], dtype='datetime64[us]')
          ys = np.array([])
          bounds = (np.datetime64('1980-01-01T00:00:00.000000'), 1.0,
                    np.datetime64('1980-01-01T01:39:00.000000'), 1.0)
          expected = RGB((xs, ys, np.empty((0, 3, 4))), ['index', 'y'],
                         xdensity=1, ydensity=1, bounds=bounds)
          self.assertEqual(rgb, expected)
  
  
  
  class DatashaderRegridTests(ComparisonTestCase):
      """
      Tests for datashader aggregation
      """
  
      def setUp(self):
          if ds_version <= LooseVersion('0.5.0'):
              raise SkipTest('Regridding operations require datashader>=0.6.0')
  
      def test_regrid_mean(self):
          img = Image((range(10), range(5), np.arange(10) * np.arange(5)[np.newaxis].T))
          regridded = regrid(img, width=2, height=2, dynamic=False)
          expected = Image(([2., 7.], [0.75, 3.25], [[1, 5], [6, 22]]))
          self.assertEqual(regridded, expected)
  
      def test_regrid_mean_xarray_transposed(self):
          img = Image((range(10), range(5), np.arange(10) * np.arange(5)[np.newaxis].T),
                      datatype=['xarray'])
          img.data = img.data.transpose()
          regridded = regrid(img, width=2, height=2, dynamic=False)
          expected = Image(([2., 7.], [0.75, 3.25], [[1, 5], [6, 22]]))
          self.assertEqual(regridded, expected)
  
      def test_regrid_rgb_mean(self):
          arr = (np.arange(10) * np.arange(5)[np.newaxis].T).astype('f')
          rgb = RGB((range(10), range(5), arr, arr*2, arr*2))
          regridded = regrid(rgb, width=2, height=2, dynamic=False)
          new_arr = np.array([[1.6, 5.6], [6.4, 22.4]])
          expected = RGB(([2., 7.], [0.75, 3.25], new_arr, new_arr*2, new_arr*2), datatype=['xarray'])
          self.assertEqual(regridded, expected)
  
      def test_regrid_max(self):
          img = Image((range(10), range(5), np.arange(10) * np.arange(5)[np.newaxis].T))
          regridded = regrid(img, aggregator='max', width=2, height=2, dynamic=False)
          expected = Image(([2., 7.], [0.75, 3.25], [[8, 18], [16, 36]]))
          self.assertEqual(regridded, expected)
  
      def test_regrid_upsampling(self):
          img = Image(([0.5, 1.5], [0.5, 1.5], [[0, 1], [2, 3]]))
          regridded = regrid(img, width=4, height=4, upsample=True, dynamic=False)
          expected = Image(([0.25, 0.75, 1.25, 1.75], [0.25, 0.75, 1.25, 1.75],
                            [[0, 0, 1, 1],
                             [0, 0, 1, 1],
                             [2, 2, 3, 3],
                             [2, 2, 3, 3]]))
          self.assertEqual(regridded, expected)
  
      def test_regrid_upsampling_linear(self):
          img = Image(([0.5, 1.5], [0.5, 1.5], [[0, 1], [2, 3]]))
          regridded = regrid(img, width=4, height=4, upsample=True, interpolation='linear', dynamic=False)
          expected = Image(([0.25, 0.75, 1.25, 1.75], [0.25, 0.75, 1.25, 1.75],
                            [[0, 0, 0, 1],
                             [0, 1, 1, 1],
                             [1, 1, 2, 2],
                             [2, 2, 2, 3]]))
          self.assertEqual(regridded, expected)
  
      def test_regrid_disabled_upsampling(self):
          img = Image(([0.5, 1.5], [0.5, 1.5], [[0, 1], [2, 3]]))
          regridded = regrid(img, width=3, height=3, dynamic=False, upsample=False)
          self.assertEqual(regridded, img)
  
      def test_regrid_disabled_expand(self):
          img = Image(([0.5, 1.5], [0.5, 1.5], [[0., 1.], [2., 3.]]))
          regridded = regrid(img, width=2, height=2, x_range=(-2, 4), y_range=(-2, 4), expand=False,
                             dynamic=False)
          self.assertEqual(regridded, img)
  
      def test_regrid_zero_range(self):
          ls = np.linspace(0, 10, 200)
          xx, yy = np.meshgrid(ls, ls)
          img = Image(np.sin(xx)*np.cos(yy), bounds=(0, 0, 1, 1))
          regridded = regrid(img, x_range=(-1, -0.5), y_range=(-1, -0.5), dynamic=False)
          expected = Image(np.zeros((0, 0)), bounds=(0, 0, 0, 0), xdensity=1, ydensity=1)
          self.assertEqual(regridded, expected)
  
  
  
  class DatashaderRasterizeTests(ComparisonTestCase):
      """
      Tests for datashader aggregation
      """
  
      def setUp(self):
          if ds_version <= LooseVersion('0.6.4'):
              raise SkipTest('Regridding operations require datashader>=0.7.0')
  
          self.simplexes = [(0, 1, 2), (3, 2, 1)]
          self.vertices = [(0., 0.), (0., 1.), (1., 0), (1, 1)]
          self.simplexes_vdim = [(0, 1, 2, 0.5), (3, 2, 1, 1.5)]
          self.vertices_vdim = [(0., 0., 1), (0., 1., 2), (1., 0, 3), (1, 1, 4)]
  
      def test_rasterize_trimesh_no_vdims(self):
          trimesh = TriMesh((self.simplexes, self.vertices))
          img = rasterize(trimesh, width=3, height=3, dynamic=False)
          image = Image(np.array([[True, True, True], [True, True, True], [True, True, True]]),
                        bounds=(0, 0, 1, 1), vdims=Dimension('Any', nodata=0))
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_no_vdims_zero_range(self):
          trimesh = TriMesh((self.simplexes, self.vertices))
          img = rasterize(trimesh, height=2, x_range=(0, 0), dynamic=False)
          image = Image(([], [0.25, 0.75], np.zeros((2, 0))),
                        bounds=(0, 0, 0, 1), xdensity=1, vdims=Dimension('Any', nodata=0))
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_with_vdims_as_wireframe(self):
          trimesh = TriMesh((self.simplexes_vdim, self.vertices), vdims=['z'])
          img = rasterize(trimesh, width=3, height=3, aggregator='any', interpolation=None, dynamic=False)
          array = np.array([
              [True, True, True],
              [True, True, True],
              [True, True, True]
          ])
          image = Image(array, bounds=(0, 0, 1, 1), vdims=Dimension('Any', nodata=0))
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh(self):
          trimesh = TriMesh((self.simplexes_vdim, self.vertices), vdims=['z'])
          img = rasterize(trimesh, width=3, height=3, dynamic=False)
          array = np.array([
              [   1.5,    1.5, np.NaN],
              [   0.5,    1.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_pandas_trimesh_implicit_nodes(self):
          simplex_df = pd.DataFrame(self.simplexes, columns=['v0', 'v1', 'v2'])
          vertex_df = pd.DataFrame(self.vertices_vdim, columns=['x', 'y', 'z'])
  
          trimesh = TriMesh((simplex_df, vertex_df))
          img = rasterize(trimesh, width=3, height=3, dynamic=False)
  
          array = np.array([
              [    2.,     3., np.NaN],
              [   1.5,    2.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_dask_trimesh_implicit_nodes(self):
          simplex_df = pd.DataFrame(self.simplexes, columns=['v0', 'v1', 'v2'])
          vertex_df = pd.DataFrame(self.vertices_vdim, columns=['x', 'y', 'z'])
  
          simplex_ddf = dd.from_pandas(simplex_df, npartitions=2)
          vertex_ddf = dd.from_pandas(vertex_df, npartitions=2)
  
          trimesh = TriMesh((simplex_ddf, vertex_ddf))
  
          ri = rasterize.instance()
          img = ri(trimesh, width=3, height=3, dynamic=False, precompute=True)
  
          cache = ri._precomputed
          self.assertEqual(len(cache), 1)
          self.assertIn(trimesh._plot_id, cache)
          self.assertIsInstance(cache[trimesh._plot_id]['mesh'], dd.DataFrame)
  
          array = np.array([
              [    2.,     3., np.NaN],
              [   1.5,    2.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_dask_trimesh(self):
          simplex_df = pd.DataFrame(self.simplexes_vdim, columns=['v0', 'v1', 'v2', 'z'])
          vertex_df = pd.DataFrame(self.vertices, columns=['x', 'y'])
  
          simplex_ddf = dd.from_pandas(simplex_df, npartitions=2)
          vertex_ddf = dd.from_pandas(vertex_df, npartitions=2)
  
          tri_nodes = Nodes(vertex_ddf, ['x', 'y', 'index'])
          trimesh = TriMesh((simplex_ddf, tri_nodes), vdims=['z'])
  
          ri = rasterize.instance()
          img = ri(trimesh, width=3, height=3, dynamic=False, precompute=True)
  
          cache = ri._precomputed
          self.assertEqual(len(cache), 1)
          self.assertIn(trimesh._plot_id, cache)
          self.assertIsInstance(cache[trimesh._plot_id]['mesh'], dd.DataFrame)
  
          array = np.array([
              [   1.5,    1.5, np.NaN],
              [   0.5,    1.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_dask_trimesh_with_node_vdims(self):
          simplex_df = pd.DataFrame(self.simplexes, columns=['v0', 'v1', 'v2'])
          vertex_df = pd.DataFrame(self.vertices_vdim, columns=['x', 'y', 'z'])
  
          simplex_ddf = dd.from_pandas(simplex_df, npartitions=2)
          vertex_ddf = dd.from_pandas(vertex_df, npartitions=2)
  
          tri_nodes = Nodes(vertex_ddf, ['x', 'y', 'index'], ['z'])
          trimesh = TriMesh((simplex_ddf, tri_nodes))
  
          ri = rasterize.instance()
          img = ri(trimesh, width=3, height=3, dynamic=False, precompute=True)
  
          cache = ri._precomputed
          self.assertEqual(len(cache), 1)
          self.assertIn(trimesh._plot_id, cache)
          self.assertIsInstance(cache[trimesh._plot_id]['mesh'], dd.DataFrame)
  
          array = np.array([
              [    2.,     3., np.NaN],
              [   1.5,    2.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_node_vdim_precedence(self):
          nodes = Points(self.vertices_vdim, vdims=['node_z'])
          trimesh = TriMesh((self.simplexes_vdim, nodes), vdims=['z'])
          img = rasterize(trimesh, width=3, height=3, dynamic=False)
  
          array = np.array([
              [    2.,     3., np.NaN],
              [   1.5,    2.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1), vdims='node_z')
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_node_explicit_vdim(self):
          nodes = Points(self.vertices_vdim, vdims=['node_z'])
          trimesh = TriMesh((self.simplexes_vdim, nodes), vdims=['z'])
          img = rasterize(trimesh, width=3, height=3, dynamic=False, aggregator=ds.mean('z'))
  
          array = np.array([
              [   1.5,    1.5, np.NaN],
              [   0.5,    1.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_zero_range(self):
          trimesh = TriMesh((self.simplexes_vdim, self.vertices), vdims=['z'])
          img = rasterize(trimesh, x_range=(0, 0), height=2, dynamic=False)
          image = Image(([], [0.25, 0.75], np.zeros((2, 0))),
                        bounds=(0, 0, 0, 1), xdensity=1)
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_vertex_vdims(self):
          simplices = [(0, 1, 2), (3, 2, 1)]
          vertices = [(0., 0., 1), (0., 1., 2), (1., 0., 3), (1., 1., 4)]
          trimesh = TriMesh((simplices, Points(vertices, vdims='z')))
          img = rasterize(trimesh, width=3, height=3, dynamic=False)
          image = Image(np.array([[2., 3., np.NaN], [1.5, 2.5, np.NaN], [np.NaN, np.NaN, np.NaN]]),
                        bounds=(0, 0, 1, 1), vdims='z')
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_ds_aggregator(self):
          trimesh = TriMesh((self.simplexes_vdim, self.vertices), vdims=['z'])
          img = rasterize(trimesh, width=3, height=3, dynamic=False, aggregator=ds.mean('z'))
          array = np.array([
              [   1.5,    1.5, np.NaN],
              [   0.5,    1.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_trimesh_string_aggregator(self):
          trimesh = TriMesh((self.simplexes_vdim, self.vertices), vdims=['z'])
          img = rasterize(trimesh, width=3, height=3, dynamic=False, aggregator='mean')
          array = np.array([
              [   1.5,    1.5, np.NaN],
              [   0.5,    1.5, np.NaN],
              [np.NaN, np.NaN, np.NaN]
          ])
          image = Image(array, bounds=(0, 0, 1, 1))
          self.assertEqual(img, image)
  
      def test_rasterize_quadmesh(self):
          qmesh = QuadMesh(([0, 1], [0, 1], np.array([[0, 1], [2, 3]])))
          img = rasterize(qmesh, width=3, height=3, dynamic=False, aggregator=ds.mean('z'))
          image = Image(np.array([[2, 3, 3], [2, 3, 3], [0, 1, 1]]),
                        bounds=(-.5, -.5, 1.5, 1.5))
          self.assertEqual(img, image)
  
      def test_rasterize_quadmesh_string_aggregator(self):
          qmesh = QuadMesh(([0, 1], [0, 1], np.array([[0, 1], [2, 3]])))
          img = rasterize(qmesh, width=3, height=3, dynamic=False, aggregator='mean')
          image = Image(np.array([[2, 3, 3], [2, 3, 3], [0, 1, 1]]),
                        bounds=(-.5, -.5, 1.5, 1.5))
          self.assertEqual(img, image)
  
      def test_rasterize_points(self):
          points = Points([(0.2, 0.3), (0.4, 0.7), (0, 0.99)])
          img = rasterize(points, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 0]]),
                           vdims=[Dimension('Count', nodata=0)])
          self.assertEqual(img, expected)
  
      def test_rasterize_curve(self):
          curve = Curve([(0.2, 0.3), (0.4, 0.7), (0.8, 0.99)])
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [1, 1]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = rasterize(curve, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          self.assertEqual(img, expected)
  
      def test_rasterize_ndoverlay(self):
          ds = Dataset([(0.2, 0.3, 0), (0.4, 0.7, 1), (0, 0.99, 2)], kdims=['x', 'y', 'z'])
          ndoverlay = ds.to(Points, ['x', 'y'], [], 'z').overlay()
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 0]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = rasterize(ndoverlay, dynamic=False, x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          self.assertEqual(img, expected)
  
      def test_rasterize_path(self):
          path = Path([[(0.2, 0.3), (0.4, 0.7)], [(0.4, 0.7), (0.8, 0.99)]])
          expected = Image(([0.25, 0.75], [0.25, 0.75], [[1, 0], [2, 1]]),
                           vdims=[Dimension('Count', nodata=0)])
          img = rasterize(path, dynamic=False,  x_range=(0, 1), y_range=(0, 1),
                          width=2, height=2)
          self.assertEqual(img, expected)
  
      def test_rasterize_image(self):
          img = Image((range(10), range(5), np.arange(10) * np.arange(5)[np.newaxis].T))
          regridded = regrid(img, width=2, height=2, dynamic=False)
          expected = Image(([2., 7.], [0.75, 3.25], [[1, 5], [6, 22]]))
          self.assertEqual(regridded, expected)
  
      def test_rasterize_image_string_aggregator(self):
          img = Image((range(10), range(5), np.arange(10) * np.arange(5)[np.newaxis].T))
          regridded = regrid(img, width=2, height=2, dynamic=False, aggregator='mean')
          expected = Image(([2., 7.], [0.75, 3.25], [[1, 5], [6, 22]]))
          self.assertEqual(regridded, expected)
  
  
  class DatashaderSpreadTests(ComparisonTestCase):
  
      def test_spread_rgb_1px(self):
          arr = np.array([[[0, 0, 0], [0, 1, 1], [0, 1, 1]],
                          [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
                          [[0, 0, 0], [0, 0, 0], [0, 0, 0]]], dtype=np.uint8).T*255
          spreaded = spread(RGB(arr))
          arr = np.array([[[0, 0, 1], [0, 0, 1], [0, 0, 1]],
                          [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
                          [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
                          [[1, 1, 1], [1, 1, 1], [1, 1, 1]]], dtype=np.uint8).T*255
          self.assertEqual(spreaded, RGB(arr))
  
      def test_spread_img_1px(self):
          if ds_version < LooseVersion('0.12.0'):
              raise SkipTest('Datashader does not support DataArray yet')
          arr = np.array([[0, 0, 0], [0, 0, 0], [1, 1, 1]]).T
          spreaded = spread(Image(arr))
          arr = np.array([[0, 0, 0], [2, 3, 2], [2, 3, 2]]).T
          self.assertEqual(spreaded, Image(arr))
  
  
  class DatashaderStackTests(ComparisonTestCase):
  
      def setUp(self):
          self.rgb1_arr = np.array([[[0, 1], [1, 0]],
                                    [[1, 0], [0, 1]],
                                    [[0, 0], [0, 0]]], dtype=np.uint8).T*255
          self.rgb2_arr = np.array([[[0, 0], [0, 0]],
                                    [[0, 0], [0, 0]],
                                    [[1, 0], [0, 1]]], dtype=np.uint8).T*255
          self.rgb1 = RGB(self.rgb1_arr)
          self.rgb2 = RGB(self.rgb2_arr)
  
  
      def test_stack_add_compositor(self):
          combined = stack(self.rgb1*self.rgb2, compositor='add')
          arr = np.array([[[0, 255, 255], [255,0, 0]], [[255, 0, 0], [0, 255, 255]]], dtype=np.uint8)
          expected = RGB(arr)
          self.assertEqual(combined, expected)
  
      def test_stack_over_compositor(self):
          combined = stack(self.rgb1*self.rgb2, compositor='over')
          self.assertEqual(combined, self.rgb2)
  
      def test_stack_over_compositor_reverse(self):
          combined = stack(self.rgb2*self.rgb1, compositor='over')
          self.assertEqual(combined, self.rgb1)
  
      def test_stack_saturate_compositor(self):
          combined = stack(self.rgb1*self.rgb2, compositor='saturate')
          self.assertEqual(combined, self.rgb1)
  
      def test_stack_saturate_compositor_reverse(self):
          combined = stack(self.rgb2*self.rgb1, compositor='saturate')
          self.assertEqual(combined, self.rgb2)
  
  
  class GraphBundlingTests(ComparisonTestCase):
  
      def setUp(self):
          if ds_version <= LooseVersion('0.7.0'):
              raise SkipTest('Regridding operations require datashader>=0.7.0')
          self.source = np.arange(8)
          self.target = np.zeros(8)
          self.graph = Graph(((self.source, self.target),))
  
      def test_directly_connect_paths(self):
          direct = directly_connect_edges(self.graph)._split_edgepaths
          self.assertEqual(direct, self.graph.edgepaths)
  
  class InspectorTests(ComparisonTestCase):
      """
      Tests for inspector operations
      """
      def setUp(self):
          points = Points([(0.2, 0.3), (0.4, 0.7), (0, 0.99)])
          self.pntsimg = rasterize(points, dynamic=False,
                               x_range=(0, 1), y_range=(0, 1), width=4, height=4)
          if spatialpandas is None:
              return
  
          xs1 = [1, 2, 3]; xs2 = [6, 7, 3];ys1 = [2, 0, 7]; ys2 = [7, 5, 2]
          holes = [ [[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]],]
          polydata = [{'x': xs1, 'y': ys1, 'holes': holes, 'z': 1},
                      {'x': xs2, 'y': ys2, 'holes': [[]], 'z': 2}]
          self.polysrgb = datashade(Polygons(polydata, vdims=['z'],
                                             datatype=['spatialpandas']),
                                    x_range=(0, 7), y_range=(0, 7), dynamic=False)
  
      def tearDown(self):
          Tap.x, Tap.y = None, None
  
  
      def test_inspect_points_or_polygons(self):
          if spatialpandas is None:
              raise SkipTest('Polygon inspect tests require spatialpandas')
          polys = inspect(self.polysrgb,
                          max_indicators=3, dynamic=False, pixels=1, x=6, y=5)
          self.assertEqual(polys, Polygons([{'x': [6, 3, 7], 'y': [7, 2, 5], 'z': 2}], vdims='z'))
          points = inspect(self.pntsimg, max_indicators=3, dynamic=False, pixels=1, x=-0.1, y=-0.1)
          self.assertEqual(points.dimension_values('x'), np.array([]))
          self.assertEqual(points.dimension_values('y'), np.array([]))
  
      def test_points_inspection_1px_mask(self):
          points = inspect_points(self.pntsimg, max_indicators=3, dynamic=False, pixels=1, x=-0.1, y=-0.1)
          self.assertEqual(points.dimension_values('x'), np.array([]))
          self.assertEqual(points.dimension_values('y'), np.array([]))
  
      def test_points_inspection_2px_mask(self):
          points = inspect_points(self.pntsimg, max_indicators=3, dynamic=False, pixels=2, x=-0.1, y=-0.1)
          self.assertEqual(points.dimension_values('x'), np.array([0.2]))
          self.assertEqual(points.dimension_values('y'), np.array([0.3]))
  
      def test_points_inspection_4px_mask(self):
          points = inspect_points(self.pntsimg, max_indicators=3, dynamic=False, pixels=4, x=-0.1, y=-0.1)
          self.assertEqual(points.dimension_values('x'), np.array([0.2, 0.4]))
          self.assertEqual(points.dimension_values('y'), np.array([0.3, 0.7]))
  
      def test_points_inspection_5px_mask(self):
          points = inspect_points(self.pntsimg, max_indicators=3, dynamic=False, pixels=5, x=-0.1, y=-0.1)
          self.assertEqual(points.dimension_values('x'), np.array([0.2, 0.4, 0]))
          self.assertEqual(points.dimension_values('y'), np.array([0.3, 0.7, 0.99]))
  
      def test_inspection_5px_mask_points_df(self):
          inspector = inspect.instance(max_indicators=3, dynamic=False, pixels=5,
                                       x=-0.1, y=-0.1)
          inspector(self.pntsimg)
          self.assertEqual(list(inspector.hits['x']),[0.2,0.4,0.0])
          self.assertEqual(list(inspector.hits['y']),[0.3,0.7,0.99])
  
      def test_points_inspection_dict_streams(self):
          Tap.x, Tap.y = 0.4, 0.7
          points = inspect_points(self.pntsimg, max_indicators=3, dynamic=True,
                                  pixels=1, streams=dict(x=Tap.param.x, y=Tap.param.y))
          self.assertEqual(len(points.streams), 1)
          self.assertEqual(isinstance(points.streams[0], Tap), True)
          self.assertEqual(points.streams[0].x, 0.4)
          self.assertEqual(points.streams[0].y, 0.7)
  
      def test_points_inspection_dict_streams_instance(self):
          Tap.x, Tap.y = 0.2, 0.3
          inspector = inspect_points.instance(max_indicators=3, dynamic=True, pixels=1,
                                              streams=dict(x=Tap.param.x, y=Tap.param.y))
          points = inspector(self.pntsimg)
          self.assertEqual(len(points.streams), 1)
          self.assertEqual(isinstance(points.streams[0], Tap), True)
          self.assertEqual(points.streams[0].x, 0.2)
          self.assertEqual(points.streams[0].y, 0.3)
  
      def test_polys_inspection_1px_mask_hit(self):
          if spatialpandas is None:
              raise SkipTest('Polygon inspect tests require spatialpandas')
          polys = inspect_polygons(self.polysrgb,
                                   max_indicators=3, dynamic=False, pixels=1, x=6, y=5)
          self.assertEqual(polys, Polygons([{'x': [6, 3, 7], 'y': [7, 2, 5], 'z': 2}],
                                           vdims='z'))
  
  
      def test_inspection_1px_mask_poly_df(self):
          if spatialpandas is None:
              raise SkipTest('Polygon inspect tests require spatialpandas')
          inspector = inspect.instance(max_indicators=3, dynamic=False, pixels=1, x=6, y=5)
          inspector(self.polysrgb)
          self.assertEqual(len(inspector.hits), 1)
          data = [[6.0, 7.0, 3.0, 2.0, 7.0, 5.0, 6.0, 7.0]]
          self.assertEqual(inspector.hits.iloc[0].geometry,
                           spatialpandas.geometry.polygon.Polygon(data))
  
  
      def test_polys_inspection_1px_mask_miss(self):
          if spatialpandas is None:
              raise SkipTest('Polygon inspect tests require spatialpandas')
          polys = inspect_polygons(self.polysrgb,
                                   max_indicators=3, dynamic=False, pixels=1, x=0, y=0)
          self.assertEqual(polys, Polygons([], vdims='z'))
  
  
  @pytest.mark.parametrize("dtype", [np.uint8, np.uint16, np.uint32])
  def test_uint_dtype(dtype):
      df = pd.DataFrame(np.arange(2, dtype=dtype), columns=["A"])
      curve = Curve(df)
      img = rasterize(curve, dynamic=False, height=10, width=10)
      assert (np.asarray(img.data["Count"]) == np.eye(10)).all()
  
  
  def test_uint64_dtype():
      df = pd.DataFrame(np.arange(2, dtype=np.uint64), columns=["A"])
      curve = Curve(df)
      with pytest.raises(TypeError, match="Dtype of uint64 for column A is not supported."):
          rasterize(curve, dynamic=False, height=10, width=10)
  