Hi sorry for the lag, definitely understand the issue better now.
I think you can manually map the values that are “zero” to a very very small number and that will solve the problem of getting see-thru pixels. In fact, if you do that, you can use colormap.set_under
to choose literally any color you want to correspond to those bins.
I recommend using the color
module in scikit image to take the L and a*/b* values (which it sounds like you figured out how to get those?) and make them into RGB values that matplotlib can understand.
For example, here’s a code snippet I used to make plots for a recent paper that needed to be “3-way diverging” with a specific color scheme, where values below zero are grey:
vmax_long = 5.7
total_colors = 5000
three_colors = [[ 56/255 , 125/255 , 156/255 ],
[ 63/255 , 138/255 , 92/255 ],
[163/255 , 85/255 , 153/255 ]]
# need to add a dim, since skimage expects >=2d images
lab_colors = color.rgb2lab(np.array([three_colors]), illuminant='D65', observer='2')[0]
def bruno_div_map(n, left_lab, right_lab, midpoint_l, target_l=None):
l, a, b = 0, 1, 2
left_l = left_lab[l] if target_l is None else target_l
right_l = right_lab[l] if target_l is None else target_l
t = np.linspace(0, 1, n)
lab_a = np.interp(t, [0, 1], [left_lab[a], right_lab[a]])
lab_b = np.interp(t, [0, 1], [left_lab[b], right_lab[b]])
lab_l = np.interp(t, [0, 1/2, 1], [left_l, midpoint_l, right_l])
return list(zip(lab_l, lab_a, lab_b))
# a very hack-y way to figure out how many colors to return so that the peaks
# of lumonisity align with the points I care about in the data. Basically,
# artificially place the sawtooth peak at T=3.5
num_grey = int(((0 - (-1))/(vmax_long - (-1))) * total_colors) - 1
num_left = int(((3.5 - 0)/(vmax_long - (-1))) * total_colors)
num_right = int(((vmax_long - 3.5)/(vmax_long - (-1))) * total_colors)
num_sim = 15 # whatever, man, matplotlib smoothes
# two sawtooths in luminosity concatenated
blended_divs = np.concatenate((
bruno_div_map(num_left, lab_colors[0], lab_colors[1], midpoint_l=80, target_l=50),
bruno_div_map(num_right, lab_colors[1], lab_colors[2], midpoint_l=80, target_l=50)[1:]
))
# now make back into rgb
bgr_long = color.lab2rgb(np.array([blended_divs]), illuminant='D65', observer='2')[0]
# and manually add the grey
bgr_long = np.concatenate((np.tile(grey, (num_grey, 1)), bgr_long))
# now we have the list of colors, make them into a cmap
cmap_long = mpl.colors.ListedColormap(bgr_long)
# specify the range of values the colors should map to
long_cnorm_continuous = mpl.colors.Normalize(vmin=-1, vmax=vmax_long)
# for setting axis ticks and labels
long_locator = ticker.MultipleLocator(1)
long_formatter = ticker.FuncFormatter(lambda i, _: f'$T_{{{int(i)}}}$' if i >= 0 else '$G_0$')
# for passing to colorbar
long_sm_continuous = mpl.cm.ScalarMappable(norm=long_cnorm_continuous, cmap=cmap_long)
long_sm_continuous.set_array([])
Sorry that the code is quite messy, it was never meant to be seen by anyone.
For your case, you probably want to use .set_under
instead of manually slotting the “under” color in like I do.