Spaces:
Sleeping
Sleeping
File size: 10,154 Bytes
8758f90 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 | # Would like to actually count the pixels of each color to see how that histogram turns out...
from bokeh.models import ColumnDataSource, Div, LinearColorMapper, Slider
from bokeh.plotting import column, curdoc, figure, row
import colorcet
from dataclasses import dataclass
import datashader as ds
import numpy as np
import pandas as pd
hows = ["linear", "log", "eq_hist", "eq_hist_new"]
@dataclass(frozen=True)
class Count:
canvas_size: int
agg: np.ndarray
mask: np.ndarray
unique_values: np.ndarray
unique_counts: np.ndarray
cdf: np.ndarray
@dataclass
class Transformed:
how: str
counts: np.ndarray
agg: np.ndarray
unique_values: np.ndarray
def create_data(canvas_size):
nsamples = 10000
rng = np.random.default_rng(1289242)
dists = {cat: pd.DataFrame(dict([('x', rng.normal(x, s, nsamples)),
('y', rng.normal(y, s, nsamples)),
('val', val),
('cat', cat)]))
for x, y, s, val, cat in
[( 2, 2, 0.03, 10, "d1"),
( 2, -2, 0.10, 20, "d2"),
( -2, -2, 0.50, 30, "d3"),
( -2, 2, 1.00, 40, "d4"),
( 0, 0, 3.00, 50, "d5")] }
df = pd.concat(dists, ignore_index=True)
df["cat"]=df["cat"].astype("category")
cvs = ds.Canvas(plot_width=canvas_size, plot_height=canvas_size)
agg = cvs.points(source=df, x="x", y="y")
agg = agg.data.ravel()
mask = agg != 0 # Mask of non-zero values
agg = agg[mask] # Only non-zero values
unique_values, unique_counts = np.unique(agg, return_counts=True) # Unique values
cdf = np.cumsum(unique_counts) / unique_counts.sum()
return Count(canvas_size, agg, mask, unique_values, unique_counts, cdf)
def process(how, count, eq_hist_new_power):
# eq_hist_new_power only used if how == "eq_hist_new".
counts = count.unique_counts
if how == "linear":
transformed_unique_values = count.unique_values.copy()
transformed_agg = count.agg.copy()
elif how == "log":
transformed_unique_values = np.log1p(count.unique_values)
transformed_agg = np.log1p(count.agg)
elif how == "eq_hist":
# Transform based on CDF
cdf = count.cdf
cdf = (cdf - cdf[0]) / (cdf[-1] - cdf[0]) # Normalised to range 0..1
transformed_unique_values = np.interp(count.unique_values, count.unique_values, cdf)
transformed_agg = np.interp(count.agg, count.unique_values, cdf)
elif how == "eq_hist_new":
# Transform based on CDF
counts = counts**eq_hist_new_power # Raise to power <= 1
cdf = np.cumsum(counts)
cdf = (cdf - cdf[0]) / (cdf[-1] - cdf[0]) # Normalised to range 0..1
transformed_unique_values = np.interp(count.unique_values, count.unique_values, cdf)
transformed_agg = np.interp(count.agg, count.unique_values, cdf)
else:
raise RuntimeError("Not implemented")
return Transformed(how, counts, transformed_agg, transformed_unique_values)
def shade(count, transformed, cmap):
low = transformed.agg.min()
high = transformed.agg.max()
rspan, gspan, bspan = np.array(list(zip(*map(ds.colors.rgb, cmap))))
span = np.linspace(low, high, len(cmap))
r = np.interp(transformed.agg, span, rspan, left=255).astype(np.uint8)
g = np.interp(transformed.agg, span, gspan, left=255).astype(np.uint8)
b = np.interp(transformed.agg, span, bspan, left=255).astype(np.uint8)
a = np.full_like(r, 255)
rgba = np.column_stack([r, g, b, a])
canvas_size = count.canvas_size
image = np.zeros((canvas_size*canvas_size), dtype=np.uint32)
view = image.view(dtype=np.uint8).reshape((canvas_size*canvas_size, 4))
for i in range(4):
view[count.mask, i] = rgba[:, i]
image.shape = (canvas_size, canvas_size)
return image
count = None
data_cds = {}
image_cds = {}
histogram_cds = {}
data_color_mappers = {}
histogram_color_mappers = {}
def create_all_data():
global count
count = create_data(canvas_size)
for how in hows:
transformed = process(how, count, eq_hist_new_power)
image = shade(count, transformed, cmap)
hist, edges = np.histogram(transformed.agg, bins=nbins)
if how == "eq_hist_new":
hist = hist**eq_hist_new_power
data = dict(count=count.unique_values, transformed=transformed.unique_values,
cdf=count.cdf)
if how in data_cds:
data_cds[how].data = data
else:
data_cds[how] = ColumnDataSource(data)
data = dict(image=[image])
if how in image_cds:
image_cds[how].data = data
else:
image_cds[how] = ColumnDataSource(data)
data = dict(hist=hist, left=edges[:-1], right=edges[1:])
if how in histogram_cds:
histogram_cds[how].data = data
else:
histogram_cds[how] = ColumnDataSource(data)
def create_color_mappers():
for how in hows:
low = data_cds[how].data["transformed"][0]
high = data_cds[how].data["transformed"][-1]
if how in data_color_mappers:
data_color_mappers[how].low = low
data_color_mappers[how].high = high
else:
data_color_mappers[how] = LinearColorMapper(palette=cmap, low=low, high=high)
low = histogram_cds[how].data["left"][0]
high = histogram_cds[how].data["left"][-1]
if how in histogram_color_mappers:
histogram_color_mappers[how].low = low
histogram_color_mappers[how].high = high
else:
histogram_color_mappers[how] = LinearColorMapper(palette=cmap, low=low, high=high)
nbins = 100
canvas_size = 200
cmap = colorcet.rainbow
eq_hist_new_power = 0.5
h = 200
w = 350
lw = 2 # line width
ms = 7 # marker size
lc = "silver" # line color
create_all_data()
create_color_mappers()
cols = []
for i, how in enumerate(hows):
# Datashaded image.
p0 = figure(width=w, height=w, title=f"how = '{how}'", toolbar_location=None)
p0.x_range.range_padding = p0.y_range.range_padding = 0
p0.image_rgba(source=image_cds[how], image="image", x=0, y=0, dw=canvas_size, dh=canvas_size)
# Transformed data space.
p1 = figure(width=w, height=h, x_axis_label="Data space", y_axis_label="Transformed data space",
title="Transform", toolbar_location=None)
p1.line(source=data_cds[how], x="count", y="transformed", line_width=lw, color=lc)
p1.scatter(source=data_cds[how], x="count", y="transformed", marker="o", size=ms,
color=dict(field="transformed", transform=data_color_mappers[how]))
# Linear histogram.
p2 = figure(width=w, height=h, title="Linear histogram in transformed data space",
x_axis_label="Transformed data space (= color space)", y_axis_label="Pixel counts",
toolbar_location=None)
p2.quad(source=histogram_cds[how], top="hist", bottom=0, left="left", right="right",
color=dict(field="left", transform=histogram_color_mappers[how]))
# Log histogram.
p3 = figure(width=w, height=h, title="Log histogram in transformed data space",
x_axis_label="Transformed data space (= color space)", y_axis_label="Pixel counts",
y_axis_type="log", toolbar_location=None)
p3.quad(source=histogram_cds[how], top="hist", bottom=1, left="left", right="right",
color=dict(field="left", transform=histogram_color_mappers[how]))
# CDF.
p4 = figure(width=w, height=h, title="CDF in transformed data space", y_axis_label="CDF",
x_axis_label="Transformed data space (= color space)", toolbar_location=None)
p4.line(source=data_cds[how], x="transformed", y="cdf", line_width=lw, color=lc)
p4.step(source=data_cds[how], x="transformed", y="cdf", line_width=lw, mode="after", color=lc)
p4.scatter(source=data_cds[how], x="transformed", y="cdf", marker="o", size=ms,
color=dict(field="transformed", transform=data_color_mappers[how]))
col = column([p0, p1, p2, p3, p4])
cols.append(col)
def power_callback(_attr, _old, new_value):
global eq_hist_new_power
eq_hist_new_power = new_value
how = "eq_hist_new"
transformed = process(how, count, eq_hist_new_power)
data_cds[how].data = dict(
count=count.unique_values, transformed=transformed.unique_values, cdf=count.cdf)
image = shade(count, transformed, cmap)
image_cds[how].data = dict(image=[image])
hist, edges = np.histogram(transformed.agg, bins=nbins)
if how == "eq_hist_new":
hist = hist**eq_hist_new_power
histogram_cds[how].data = dict(hist=hist, left=edges[:-1], right=edges[1:])
def canvas_size_callback(_attr, _old, new_value):
global canvas_size
canvas_size = new_value
create_all_data()
create_color_mappers()
# Colorbar column at end, just using an image.
color_mapper = LinearColorMapper(palette=cmap, low=0, high=1)
image = np.linspace(0.0, 1.0, len(cmap))
image = np.expand_dims(image, axis=0)
p = figure(width=w, height=80, toolbar_location=None, x_range=(0, 1), y_range=(0, 1),
title="Colormap")
p.yaxis.visible = False
p.grid.visible = False
p.image(image=[image], color_mapper=color_mapper, x=0, y=0, dw=1, dh=1)
power_slider = Slider(start=0.0, end=1.0, value=eq_hist_new_power, step=0.01,
title="eq_hist_new power")
power_slider.on_change("value", power_callback)
canvas_size_slider = Slider(start=50, end=500, value=canvas_size, step=50, title="Canvas size")
canvas_size_slider.on_change("value", canvas_size_callback)
div = Div(text="<p>eq_hist_new raises the histogram bin counts to a power in the range "
"0 <= power <= 1. This reduces the height of the larger counts with respect to the lower "
"counts, bringing the histogram bins closer together where there were large gaps.</p><p>"
"power=1 corresponds to the original eq_hist algorithm, power=0 makes all counts the same, "
"power=0.5 takes square root of the counts.</p>", width=300)
col = column([p, power_slider, canvas_size_slider, div])
cols.append(col)
curdoc().add_root(row(cols))
|