Example #1
0
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)
Example #2
0
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)
Example #3
0
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)
Example #4
0
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)