def _colorize(agg, color_key, how, min_alpha, name): if cupy and isinstance(agg.data, cupy.ndarray): from ._cuda_utils import interp array = cupy.array else: interp = np.interp array = np.array if not agg.ndim == 3: raise ValueError("agg must be 3D") cats = agg.indexes[agg.dims[-1]] if color_key is None: raise ValueError("Color key must be provided, with at least as many " + "colors as there are categorical fields") if not isinstance(color_key, dict): color_key = dict(zip(cats, color_key)) if len(color_key) < len(cats): raise ValueError( "Insufficient colors provided ({}) for the categorical fields available ({})" .format(len(color_key), len(cats))) if not (0 <= min_alpha <= 255): raise ValueError( "min_alpha ({}) must be between 0 and 255".format(min_alpha)) colors = [rgb(color_key[c]) for c in cats] rs, gs, bs = map(array, zip(*colors)) # Reorient array (transposing the category dimension first) agg_t = agg.transpose(*((agg.dims[-1], ) + agg.dims[:2])) data = orient_array(agg_t).transpose([1, 2, 0]) total = data.sum(axis=2) # zero-count pixels will be 0/0, but it's safe to ignore that when dividing with np.errstate(divide='ignore', invalid='ignore'): r = (data.dot(rs) / total).astype(np.uint8) g = (data.dot(gs) / total).astype(np.uint8) b = (data.dot(bs) / total).astype(np.uint8) offset = total.min() mask = np.isnan(total) if offset == 0: mask = mask | (total <= 0) offset = total[total > 0].min() a = _normalize_interpolate_how(how)(total - offset, mask) a = interp(a, array([np.nanmin(a).item(), np.nanmax(a).item()]), array([min_alpha, 255]), left=0, right=255).astype(np.uint8) r[mask] = g[mask] = b[mask] = 255 values = np.dstack([r, g, b, a]).view(np.uint32).reshape(a.shape) if cupy and isinstance(values, cupy.ndarray): # Convert cupy array to numpy for final image values = cupy.asnumpy(values) return Image(values, dims=agg.dims[:-1], coords=OrderedDict([ (agg.dims[1], agg.coords[agg.dims[1]]), (agg.dims[0], agg.coords[agg.dims[0]]), ]), name=name)
def _colorize(agg, color_key, how, alpha, span, min_alpha, name, color_baseline): if cupy and isinstance(agg.data, cupy.ndarray): from ._cuda_utils import interp, masked_clip_2d array = cupy.array else: from ._cpu_utils import masked_clip_2d interp = np.interp array = np.array if not agg.ndim == 3: raise ValueError("agg must be 3D") cats = agg.indexes[agg.dims[-1]] if not len(cats ): # No categories and therefore no data; return an empty image return Image(np.zeros(agg.shape[0:2], dtype=np.uint32), dims=agg.dims[:-1], coords=OrderedDict([ (agg.dims[1], agg.coords[agg.dims[1]]), (agg.dims[0], agg.coords[agg.dims[0]]) ]), name=name) if color_key is None: raise ValueError("Color key must be provided, with at least as many " + "colors as there are categorical fields") if not isinstance(color_key, dict): color_key = dict(zip(cats, color_key)) if len(color_key) < len(cats): raise ValueError( "Insufficient colors provided ({}) for the categorical fields available ({})" .format(len(color_key), len(cats))) colors = [rgb(color_key[c]) for c in cats] rs, gs, bs = map(array, zip(*colors)) # Reorient array (transposing the category dimension first) agg_t = agg.transpose(*((agg.dims[-1], ) + agg.dims[:2])) data = orient_array(agg_t).transpose([1, 2, 0]) if isinstance(data, da.Array): data = data.compute() color_data = data.copy() # subtract color_baseline if needed baseline = np.nanmin( color_data) if color_baseline is None else color_baseline with np.errstate(invalid='ignore'): if baseline > 0: color_data -= baseline elif baseline < 0: color_data += -baseline if color_data.dtype.kind != 'u' and color_baseline is not None: color_data[color_data < 0] = 0 color_total = nansum_missing(color_data, axis=2) # dot does not handle nans, so replace with zeros color_data[np.isnan(data)] = 0 # zero-count pixels will be 0/0, but it's safe to ignore that when dividing with np.errstate(divide='ignore', invalid='ignore'): r = (color_data.dot(rs) / color_total).astype(np.uint8) g = (color_data.dot(gs) / color_total).astype(np.uint8) b = (color_data.dot(bs) / color_total).astype(np.uint8) # special case -- to give an appropriate color when min_alpha != 0 and data=0, # take avg color of all non-nan categories color_mask = ~np.isnan(data) cmask_sum = np.sum(color_mask, axis=2) with np.errstate(divide='ignore', invalid='ignore'): r2 = (color_mask.dot(rs) / cmask_sum).astype(np.uint8) g2 = (color_mask.dot(gs) / cmask_sum).astype(np.uint8) b2 = (color_mask.dot(bs) / cmask_sum).astype(np.uint8) missing_colors = np.sum(color_data, axis=2) == 0 r = np.where(missing_colors, r2, r) g = np.where(missing_colors, g2, g) b = np.where(missing_colors, b2, b) total = nansum_missing(data, axis=2) mask = np.isnan(total) # if span is provided, use it, otherwise produce a span based off the # min/max of the data if span is None: offset = np.nanmin(total) if total.dtype.kind == 'u' and offset == 0: mask = mask | (total == 0) # If at least one element is not masked, use the minimum as the offset # otherwise the offset remains at zero if not np.all(mask): offset = total[total > 0].min() total = np.where(~mask, total, np.nan) a_scaled = _normalize_interpolate_how(how)(total - offset, mask) norm_span = [np.nanmin(a_scaled).item(), np.nanmax(a_scaled).item()] else: if how == 'eq_hist': # For eq_hist to work with span, we'll need to compute the histogram # only on the specified span's range. raise ValueError("span is not (yet) valid to use with eq_hist") # even in fixed-span mode cells with 0 should remain fully transparent # i.e. a 0 will be fully transparent, but any non-zero number will # be clipped to the span range and have min-alpha applied offset = np.array(span, dtype=data.dtype)[0] if total.dtype.kind == 'u' and np.nanmin(total) == 0: mask = mask | (total <= 0) total = np.where(~mask, total, np.nan) masked_clip_2d(total, mask, *span) a_scaled = _normalize_interpolate_how(how)(total - offset, mask) norm_span = _normalize_interpolate_how(how)([0, span[1] - span[0]], 0) # Interpolate the alpha values a = interp(a_scaled, array(norm_span), array([min_alpha, alpha]), left=0, right=255).astype(np.uint8) values = np.dstack([r, g, b, a]).view(np.uint32).reshape(a.shape) if cupy and isinstance(values, cupy.ndarray): # Convert cupy array to numpy for final image values = cupy.asnumpy(values) return Image(values, dims=agg.dims[:-1], coords=OrderedDict([ (agg.dims[1], agg.coords[agg.dims[1]]), (agg.dims[0], agg.coords[agg.dims[0]]), ]), name=name)
def _interpolate(agg, cmap, how, alpha, span, min_alpha, name): if cupy and isinstance(agg.data, cupy.ndarray): from ._cuda_utils import masked_clip_2d, interp else: from ._cpu_utils import masked_clip_2d interp = np.interp if agg.ndim != 2: raise ValueError("agg must be 2D") interpolater = _normalize_interpolate_how(how) data = orient_array(agg).copy() # Compute mask if np.issubdtype(data.dtype, np.bool_): mask = ~data data = data.astype(np.int8) else: if np.issubdtype(data.dtype, np.integer): mask = data == 0 else: mask = np.isnan(data) # Handle case where everything is masked out if mask.all(): return Image(np.zeros(shape=agg.data.shape, dtype=np.uint32), coords=agg.coords, dims=agg.dims, attrs=agg.attrs, name=name) # Handle offset / clip if span is None: offset = np.nanmin(data[~mask]) else: offset = np.array(span, dtype=data.dtype)[0] masked_clip_2d(data, mask, *span) # If log/cbrt, could case to float64 right away # If linear, can keep current type data -= offset with np.errstate(invalid="ignore", divide="ignore"): # Transform data (log, eq_hist, etc.) data = interpolater(data, mask) # Transform span if span is None: masked_data = np.where(~mask, data, np.nan) span = np.nanmin(masked_data), np.nanmax(masked_data) else: if how == 'eq_hist': # For eq_hist to work with span, we'll need to store the histogram # from the data and then apply it to the span argument. raise ValueError("span is not (yet) valid to use with eq_hist") span = interpolater([0, span[1] - span[0]], 0) if isinstance(cmap, Iterator): cmap = list(cmap) if isinstance(cmap, list): rspan, gspan, bspan = np.array(list(zip(*map(rgb, cmap)))) span = np.linspace(span[0], span[1], len(cmap)) r = interp(data, span, rspan, left=255).astype(np.uint8) g = interp(data, span, gspan, left=255).astype(np.uint8) b = interp(data, span, bspan, left=255).astype(np.uint8) a = np.where(np.isnan(data), 0, alpha).astype(np.uint8) rgba = np.dstack([r, g, b, a]) elif isinstance(cmap, str) or isinstance(cmap, tuple): color = rgb(cmap) aspan = np.arange(min_alpha, alpha + 1) span = np.linspace(span[0], span[1], len(aspan)) r = np.full(data.shape, color[0], dtype=np.uint8) g = np.full(data.shape, color[1], dtype=np.uint8) b = np.full(data.shape, color[2], dtype=np.uint8) a = interp(data, span, aspan, left=0, right=255).astype(np.uint8) rgba = np.dstack([r, g, b, a]) elif callable(cmap): # Assume callable is matplotlib colormap scaled_data = (data - span[0]) / (span[1] - span[0]) if cupy and isinstance(scaled_data, cupy.ndarray): # Convert cupy array to numpy before passing to matplotlib colormap scaled_data = cupy.asnumpy(scaled_data) rgba = cmap(scaled_data, bytes=True) rgba[:, :, 3] = np.where(np.isnan(scaled_data), 0, alpha).astype(np.uint8) else: raise TypeError("Expected `cmap` of `matplotlib.colors.Colormap`, " "`list`, `str`, or `tuple`; got: '{0}'".format( type(cmap))) img = rgba.view(np.uint32).reshape(data.shape) if cupy and isinstance(img, cupy.ndarray): # Convert cupy array to numpy for final image img = cupy.asnumpy(img) return Image(img, coords=agg.coords, dims=agg.dims, name=name)
def _colorize(agg, color_key, how, span, min_alpha, name): if cupy and isinstance(agg.data, cupy.ndarray): from ._cuda_utils import interp, masked_clip_2d array = cupy.array else: from ._cpu_utils import masked_clip_2d interp = np.interp array = np.array if not agg.ndim == 3: raise ValueError("agg must be 3D") cats = agg.indexes[agg.dims[-1]] if color_key is None: raise ValueError("Color key must be provided, with at least as many " + "colors as there are categorical fields") if not isinstance(color_key, dict): color_key = dict(zip(cats, color_key)) if len(color_key) < len(cats): raise ValueError("Insufficient colors provided ({}) for the categorical fields available ({})" .format(len(color_key), len(cats))) if not (0 <= min_alpha <= 255): raise ValueError("min_alpha ({}) must be between 0 and 255".format(min_alpha)) colors = [rgb(color_key[c]) for c in cats] rs, gs, bs = map(array, zip(*colors)) # Reorient array (transposing the category dimension first) agg_t = agg.transpose(*((agg.dims[-1],)+agg.dims[:2])) data = orient_array(agg_t).transpose([1, 2, 0]) total = data.sum(axis=2) # zero-count pixels will be 0/0, but it's safe to ignore that when dividing with np.errstate(divide='ignore', invalid='ignore'): r = (data.dot(rs)/total).astype(np.uint8) g = (data.dot(gs)/total).astype(np.uint8) b = (data.dot(bs)/total).astype(np.uint8) mask = np.isnan(total) # if span is provided, use it, otherwise produce it a span based off the # min/max of the data if span is None: # Currently masks out zero or negative values, but will need fixing offset = np.nanmin(total) if offset == 0 and total.dtype.kind == 'u': mask = mask | (total <= 0) # If at least one element is not masked, use the minimum as the offset # otherwise the offset remains at zero if not np.all(mask): offset = total[total > 0].min() total = np.where(~mask, total, np.nan) a_scaled = _normalize_interpolate_how(how)(total - offset, mask) norm_span = [np.nanmin(a_scaled).item(), np.nanmax(a_scaled).item()] else: if how == 'eq_hist': # For eq_hist to work with span, we'll need to store the histogram # from the data and then apply it to the span argument. raise ValueError("span is not (yet) valid to use with eq_hist") # even in fixed-span mode cells with 0 should remain fully transparent # i.e. a 0 will be fully transparent, but any non-zero number will # be clipped to the span range and have min-alpha applied offset = np.array(span, dtype=data.dtype)[0] if np.nanmin(total) == 0 and total.dtype.kind == 'u': mask = mask | (total <= 0) total = np.where(~mask, total, np.nan) masked_clip_2d(total, mask, *span) a_scaled = _normalize_interpolate_how(how)(total - offset, mask) norm_span = _normalize_interpolate_how(how)([0, span[1] - span[0]], 0) # Interpolate the alpha values a = interp(a_scaled, array(norm_span), array([min_alpha, 255]), left=0, right=255).astype(np.uint8) r[mask] = g[mask] = b[mask] = 255 values = np.dstack([r, g, b, a]).view(np.uint32).reshape(a.shape) if cupy and isinstance(values, cupy.ndarray): # Convert cupy array to numpy for final image values = cupy.asnumpy(values) return Image(values, dims=agg.dims[:-1], coords=OrderedDict([ (agg.dims[1], agg.coords[agg.dims[1]]), (agg.dims[0], agg.coords[agg.dims[0]]), ]), name=name)