How should I extend a color palette? - python

I am writing a script that is to combine multiple histograms into a stack plot. The script is to be able to handle a fairly arbitrary number of histograms. I want to be able to color the histograms in the stack plot according to a defined color palette and also to be able to extend that color palette when it is insufficient to color the number of histograms that the script is to deal with.
I have created functions to handle getting the mean of colors and had in mind to try extending a defined palette by mixing colors in some automated way and then extending the palette by adding those mixed colors, but I'm not sure how to do this in a structured, sensible way. I request guidance and suggestions on how to extend a palette in an automated way.
style1 = [
"#FC0000",
"#FFAE3A",
"#00AC00",
"#6665EC",
"#A9A9A9"
]
def clamp(x):
return(max(0, min(x, 255)))
def RGB_to_HEX(RGB_tuple):
# This function returns a HEX string given an RGB tuple.
r = RGB_tuple[0]
g = RGB_tuple[1]
b = RGB_tuple[2]
return "#{0:02x}{1:02x}{2:02x}".format(clamp(r), clamp(g), clamp(b))
def HEX_to_RGB(HEX_string):
# This function returns an RGB tuple given a HEX string.
HEX = HEX_string.lstrip('#')
HEX_length = len(HEX)
return tuple(
int(HEX[i:i + HEX_length // 3], 16) for i in range(
0,
HEX_length,
HEX_length // 3
)
)
def mean_color(colorsInHEX):
# This function returns a HEX string that represents the mean color of a
# list of colors represented by HEX strings.
colorsInRGB = []
for colorInHEX in colorsInHEX:
colorsInRGB.append(HEX_to_RGB(colorInHEX))
sum_r = 0
sum_g = 0
sum_b = 0
for colorInRGB in colorsInRGB:
sum_r += colorInRGB[0]
sum_g += colorInRGB[1]
sum_b += colorInRGB[2]
mean_r = sum_r / len(colorsInRGB)
mean_g = sum_g / len(colorsInRGB)
mean_b = sum_b / len(colorsInRGB)
return RGB_to_HEX((mean_r, mean_g, mean_b))
def extend_palette(
colors = None, # a list of HEX string colors
numberOfColorsNeeded = None # number of colors to which list should be extended
)
# magic happens here
return colors_extended
print(
extend_palette(
colors = style1,
10
)
)
print(
extend_palette(
colors = style1,
50
)
)

You can use colorir for that.
from colorir import *
pal = Palette.load("spectral") # Load a categorical palette, a full list can be found in the docs
grad = Grad(pal) # Object to automatically "mix" the colors
# Now to generate a dynamic list of colors based on the number of inputs:
for i in range(15):
# Get a bigger list by interpolating the colors if necessary
if i > len(pal):
colors = grad.n_colors(i)
else:
colors = pal.colors
# Use the 'colors' list for your plots
# plt.hist(..., color=colors)

Related

Mathplotlib set plot color based on an object id

I have some python Objects with some data in them, and all of them have an id ( 1,2,3,4,5 ..... n). I have a python function where I send one of these objects and I want to plot the data from it, and the color to be different based on the id. I tried to do some things i've seen from other questions, like trying to convert the id integer into an RGB value, like this:
Blue = integer & 255
Green = (integer >> 8) & 255
Red = (integer >> 16) & 255
return (RED/255, Green/255, Blue/255) # to get them into [0,1] interval
But the problem with that is that the color will be the same between close number like 1,2,3 (basicaly will have the same color almost). Any way to do this ?
EDIT:
So i was thinking of doing something like this:
def rgb_function(object_id):
random.seed(object_id)
value = random.uniform(0,1)
#then calculate RGB value based on this answer: https://stackoverflow.com/questions/55178857/convert-a-floating-point-number-to-rgb-vector
H = value;
R = abs(H * 6 - 3) - 1;
G = 2 - abs(H * 6 - 2);
B = 2 - abs(H * 6 - 4);
return max(0, min(1, R)), max(0, min(1, G)), max(0, min(1, B))
You can probably get away with just shuffling a colormap with a wide variety of colors so that it gets randomized.
import random
import matplotlib.pyplot as plt
import numpy as np
colors = [plt.cm.hsv(i) for i in np.linspace(0, 1, 600)]
# random.seed(77) # uncomment this if you want the same colors everytime
random.shuffle(colors)
fig, ax = plt.subplots()
ax.plot(range(20), range(20), colors[:20]
# this gives different colors for ids that are similar
If you want truly random colors, you should see this answer. But maybe that is overkill.

Access Color from Plotly Color Scale

Is there a way in Plotly to access colormap colours at any value along its range?
I know I can access the defining colours for a colourscale from
plotly.colors.PLOTLY_SCALES["Viridis"]
but I am unable to find how to access intermediate / interpolated values.
The equivalent in Matplotlib is shown in this question. There is also another question that address a similar question from the colorlover library, but neither offers a nice solution.
Plotly does not appear to have such a method, so I wrote one:
import plotly.colors
def get_continuous_color(colorscale, intermed):
"""
Plotly continuous colorscales assign colors to the range [0, 1]. This function computes the intermediate
color for any value in that range.
Plotly doesn't make the colorscales directly accessible in a common format.
Some are ready to use:
colorscale = plotly.colors.PLOTLY_SCALES["Greens"]
Others are just swatches that need to be constructed into a colorscale:
viridis_colors, scale = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
colorscale = plotly.colors.make_colorscale(viridis_colors, scale=scale)
:param colorscale: A plotly continuous colorscale defined with RGB string colors.
:param intermed: value in the range [0, 1]
:return: color in rgb string format
:rtype: str
"""
if len(colorscale) < 1:
raise ValueError("colorscale must have at least one color")
if intermed <= 0 or len(colorscale) == 1:
return colorscale[0][1]
if intermed >= 1:
return colorscale[-1][1]
for cutoff, color in colorscale:
if intermed > cutoff:
low_cutoff, low_color = cutoff, color
else:
high_cutoff, high_color = cutoff, color
break
# noinspection PyUnboundLocalVariable
return plotly.colors.find_intermediate_color(
lowcolor=low_color, highcolor=high_color,
intermed=((intermed - low_cutoff) / (high_cutoff - low_cutoff)),
colortype="rgb")
The challenge is that the built-in Plotly colorscales are not consistently exposed. Some are defined as a colorscale already, others as just a list of color swatches that must be converted to a color scale first.
The Viridis colorscale is defined with hex values, which the Plotly color manipulation methods don't like, so it's easiest to construct it from swatches like this:
viridis_colors, _ = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
colorscale = plotly.colors.make_colorscale(viridis_colors)
get_continuous_color(colorscale, intermed=0.25)
# rgb(58.75, 80.75, 138.25)
There is a built in method from plotly.express.colors to sample_colorscale which would provide the color samples:
from plotly.express.colors import sample_colorscale
import plotly.graph_objects as go
import numpy as np
x = np.linspace(0, 1, 25)
c = sample_colorscale('jet', list(x))
fig = go.FigureWidget()
fig.add_trace(
go.Bar(x=x, y=y, marker_color=c)
)
fig.show()
See the output figure -> sampled_colors
This answer extend the already good one provided by Adam. In particular, it deals with the inconsistency of Plotly's color scales.
In Plotly, you specify a built-in color scale by writing colorscale="name_of_the_colorscale". This suggests that Plotly already has a built-in tool that somehow convert the color scale to an appropriate value and is capable of dealing with these inconsistencies. By searching Plotly's source code we find the useful ColorscaleValidator class. Let's see how to use it:
def get_color(colorscale_name, loc):
from _plotly_utils.basevalidators import ColorscaleValidator
# first parameter: Name of the property being validated
# second parameter: a string, doesn't really matter in our use case
cv = ColorscaleValidator("colorscale", "")
# colorscale will be a list of lists: [[loc1, "rgb1"], [loc2, "rgb2"], ...]
colorscale = cv.validate_coerce(colorscale_name)
if hasattr(loc, "__iter__"):
return [get_continuous_color(colorscale, x) for x in loc]
return get_continuous_color(colorscale, loc)
# Identical to Adam's answer
import plotly.colors
from PIL import ImageColor
def get_continuous_color(colorscale, intermed):
"""
Plotly continuous colorscales assign colors to the range [0, 1]. This function computes the intermediate
color for any value in that range.
Plotly doesn't make the colorscales directly accessible in a common format.
Some are ready to use:
colorscale = plotly.colors.PLOTLY_SCALES["Greens"]
Others are just swatches that need to be constructed into a colorscale:
viridis_colors, scale = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
colorscale = plotly.colors.make_colorscale(viridis_colors, scale=scale)
:param colorscale: A plotly continuous colorscale defined with RGB string colors.
:param intermed: value in the range [0, 1]
:return: color in rgb string format
:rtype: str
"""
if len(colorscale) < 1:
raise ValueError("colorscale must have at least one color")
hex_to_rgb = lambda c: "rgb" + str(ImageColor.getcolor(c, "RGB"))
if intermed <= 0 or len(colorscale) == 1:
c = colorscale[0][1]
return c if c[0] != "#" else hex_to_rgb(c)
if intermed >= 1:
c = colorscale[-1][1]
return c if c[0] != "#" else hex_to_rgb(c)
for cutoff, color in colorscale:
if intermed > cutoff:
low_cutoff, low_color = cutoff, color
else:
high_cutoff, high_color = cutoff, color
break
if (low_color[0] == "#") or (high_color[0] == "#"):
# some color scale names (such as cividis) returns:
# [[loc1, "hex1"], [loc2, "hex2"], ...]
low_color = hex_to_rgb(low_color)
high_color = hex_to_rgb(high_color)
return plotly.colors.find_intermediate_color(
lowcolor=low_color,
highcolor=high_color,
intermed=((intermed - low_cutoff) / (high_cutoff - low_cutoff)),
colortype="rgb",
)
At this point, all you have to do is:
get_color("phase", 0.5)
# 'rgb(123.99999999999999, 112.00000000000001, 236.0)'
import numpy as np
get_color("phase", np.linspace(0, 1, 256))
# ['rgb(167, 119, 12)',
# 'rgb(168.2941176470588, 118.0078431372549, 13.68235294117647)',
# ...
Edit: improvements to deal with special cases.
The official reference explains. Here
import plotly.express as px
print(px.colors.sequential.Viridis)
['#440154', '#482878', '#3e4989', '#31688e', '#26828e', '#1f9e89', '#35b779', '#6ece58', '#b5de2b', '#fde725']
print(px.colors.sequential.Viridis[0])
#440154
import plotly.express as px
color_list = list(name_of_color_scale)
# name_of_color_scale could be any in-built colorscale like px.colors.qualitative.D3.
Output:
color_list =
['#1F77B4',
'#FF7F0E',
'#2CA02C',
'#D62728',
'#9467BD',
'#8C564B',
'#E377C2',
'#7F7F7F',
'#BCBD22',
'#17BECF']

Color Correction Matrix in XYZ/RGB not working

I am aiming to perform a color correction based on a reference image, using color charts. As a personal goal, I'm trying to correct the colors of an image I previously modified. The chart has been affected by the same layers, of course:
Originals:
Manually modified:
I'm using the following function that I've written myself to get the matrix:
def _get_matrix_transformation(self,
observed_colors: np.ndarray,
reference_colors: np.ndarray):
"""
Args:
observed_colors: colors found in target chart
reference_colors: colors found on source/reference image
Returns:
Nothing.
"""
# case 1
observed_m = [observed_colors[..., i].mean() for i in range(observed_colors.shape[-1])]
observed_colors = (observed_colors - observed_m).astype(np.float32)
reference_m = [reference_colors[..., i].mean() for i in range(reference_colors.shape[-1])]
reference_colors = (reference_colors - reference_m).astype(np.float32)
# XYZ color conversion
observed_XYZ = cv.cvtColor(observed_colors, cv.COLOR_BGR2XYZ)
observed_XYZ = np.reshape(observed_colors, (observed_XYZ.shape[0] * observed_XYZ.shape[1],
observed_XYZ.shape[2]))
reference_XYZ = cv.cvtColor(reference_colors, cv.COLOR_BGR2XYZ)
reference_XYZ = np.reshape(reference_colors, (reference_XYZ.shape[0] * reference_XYZ.shape[1],
reference_XYZ.shape[2]))
# case 2
# mean subtraction in order to use the covariance matrix
# observed_m = [observed_XYZ[..., i].mean() for i in range(observed_XYZ.shape[-1])]
# observed_XYZ = observed_XYZ - observed_m
# reference_m = [reference_XYZ[..., i].mean() for i in range(reference_XYZ.shape[-1])]
# reference_XYZ = reference_XYZ - reference_m
# apply SVD
H = np.dot(reference_XYZ.T, observed_XYZ)
U, S, Vt = np.linalg.svd(H)
# get transformation
self._M = Vt.T * U.T
# consider reflection case
if np.linalg.det(self._M) < 0:
Vt[2, :] *= -1
self._M = Vt.T * U.T
return
I'm applying the correction like this:
def _apply_profile(self, img: np.ndarray) -> np.ndarray:
"""
Args:
img: image to be corrected.
Returns:
Corrected image.
"""
# Revert gamma compression
img = adjust_gamma(img, gamma=1/2.2)
# Apply color correction
corrected_img = cv.cvtColor(img.astype(np.float32), cv.COLOR_BGR2XYZ)
corrected_img = corrected_img.reshape((corrected_img.shape[0]*corrected_img.shape[1], corrected_img.shape[2]))
corrected_img = np.dot(self._M, corrected_img.T).T.reshape(img.shape)
corrected_img = cv.cvtColor(corrected_img.astype(np.float32), cv.COLOR_XYZ2BGR)
corrected_img = np.clip(corrected_img, 0, 255)
# Apply gamma
corrected_img = adjust_gamma(corrected_img.astype(np.uint8), gamma=2.2)
return corrected_img
The result I'm currently getting if the transformation is done in BGR (just commented color conversion functions):
In XYZ (don't pay attention to the resizing, that's because of me):
Now, I'm asking these questions:
Is inverting gamma necessary in this case? If so, am I doing it correctly? Should I implement a LUT that works with other data types such as np.float32?
Subtraction of the mean should be done in XYZ on BGR color space (case 1 vs case 2)?
Is considering the reflection case (as in a rigid body rotation problem) necessary?
Is clipping necessary? And if so, are those the correct values and data types?

thersholding limits on HSV image in python

I have read an image and I have converted the image to HSV image.
I want to apply threshold limits for hue, saturation , and value components separately.
Hue thershold 0 to 1, saturation thershold 0.28 to 1 and value thershold 0 to 0.55
I want to this application for color masking !
how to apply these limits on my image files.
image_read = cv2.imread('tryimage.jpg')
im = cv2.cvtColor(image_read,cv2.COLOR_RGB2HSV)
im_hue = im[:,:,0]
im_sat = im[:,:,1]
im_val = im[:,:,2]
# how to apply thershold ?
fig, ax = plt.subplots(nrows=1,ncols=3)
ax[0].imshow(im_hue)
ax[1].imshow(im_sat)
ax[2].imshow(im_val)
plt.show()
I have done the same in Matlab, I have taken only the pixels of my interest in each band and then merged these back to get the pixels of my interest.
Here is my matlab code snippet , which I want to do the same in python.
color.hueThresholdLow = 0;
color.hueThresholdHigh = 1;
color.saturationThresholdLow = 0;
color.saturationThresholdHigh = 0.28;
color.valueThresholdLow = 0.38;
color.valueThresholdHigh = 0.97;
maskedRGBImage = color_masking(rgbImage,color);
function color_masking(rgbImage, color)
hsvimage = rgb2hsv(rgbImage);
himage = hsvimage(:,:,1);
simage = hsvimage(:,:2);
vimage = hsvimage(:,:,3);
hMask = (hImage >= color.hueThresholdLow) & (hImage <= color.hueThresholdHigh);
sMask = (sImage >= color.saturationThresholdLow) & (sImage <= color.saturationThresholdHigh);
vMask = (vImage >= color.valueThresholdLow) & (vImage <= color.valueThresholdHigh);
ObjectsMask = uint8(hMask & sMask & vMask);
.....
In python you can write it very similar to matlab. It is usually a good idea to create a function for methods that you might use more than once, but feel free of removing the function declaration if it doesn't suit your needs.
def threshold_hsv(im_hsv, hlow, hhigh, slow, shigh, vlow, vhigh):
im_hue = im_hsv[:,:,0]
im_sat = im_hsv[:,:,1]
im_val = im_hsv[:,:,2]
h_mask = (im_hue >= hlow) & (im_hue <= hhigh)
s_mask = (im_sat >= slow) & (im_sat <= shigh)
v_mask = (im_val >= vlow) & (im_val <= vhigh)
return h_mask & s_mask & v_mask
And then you can call the function with your data as:
>>> object_mask = threshold_hsv(hsvimage, 0, 1, 0, 0.28, 0.38, 0.97)
As you can see, the syntax is pretty similar (if not identical) to that of the matlab. This holds as long as your hsvimage is a numpy array, which is what OpenCV generates in python.
To select values that satisfy your limits (and discard the ones not in the limits), use list comprehensions:
# filtered_pixels is a list of tuples, which are ordered as (h, s, v)
# i.e. filtered_pixels[0][0] = h, filtered_pixels[0][1] = s and
# filtered_pixels[0][2] = v
filtered_pixels = [(im_hue[i], im_sat[i], im_val[i]) for i in range(len(im_hue)) if satisfies_limits(im_hue[i], im_sat[i], im_val[i])]
satisfies_limits is a function that checks whether the passed hue, saturation and value are in the required limits. You can unwrap the above list comprehension to a for loop to if you wish.
To limit all values to the given limits, use the map() builtin:
clamped_hue = map(lambda h: max(hue_min, min(h, hue_max)), im_hue)
# And so on for saturation and value

How to remove rings from convolve healpix map?

I'm applying convolution techniques to convolve 2 datasets, a healpix map with nside = 256 and a primary beam of shape (256, 256) in order to measure the total intensity from the convolved healpix map. My problem is that after convolving my map with the primary beam i get rings in my convolved map. I've tried normalizing it with either lanczos or Gaussian kernel to take care of the rings but all these approaches have failed.
In my code below, i used the query function in scipy to search for the nearest pixels in my healpix map within a given radius and take the sum of the product of the corresponding pixels in the primary beam using map coordinate. The final image i get has rings in it. Please can anyone help me solve this problem? Thanks in advance.
def query_npix(nside, npix, radius):
print 'searching for nearest pixels:......'
t1, t2 = hp.pix2ang(nside, np.arange(npix))
tree = spatial.cKDTree(zip(t1, t2))
dist, ipix_indx = tree.query(zip(t1, t2), k = 150, distance_upper_bound = radius)
r1, r2 = hp.pix2ang(nside, ipix_indx)
ra = r1.T - t1
dec = r2.T - t2
print 'Done searching'
return np.array(dist), np.array(ipix_indx), np.array(ra.T), np.array(dec.T)
def fullSky_convolve(healpix_map, primary_beam_fits, ipix_indx, dist, radius, r1, r2):
measured_map = []
hdulist = openFitsFile(primary_beam_fits)
beam_data = hdulist[0].data
header = hdulist[0].header
nside = hp.get_nside(healpix_map[0, ...])
npix = hp.get_map_size(healpix_map[0, ...]) # total number of pixels in the map must be 12 * nside^2
crpix1, crval1, cdelt1 = [ header.get(x) for x in "CRPIX1", "CRVAL1", "CDELT1" ]
crpix2, crval2, cdelt2 = [ header.get(x) for x in "CRPIX2", "CRVAL2", "CDELT2" ]
# beam centres in pixel coordinates
xc = crpix1-1 + (np.rad2deg(r1.ravel()) - crval1)/(256*cdelt1)
yc = crpix2-1 + (np.rad2deg(r2.ravel()) - crval2)/(256*cdelt2)
#xc = (np.rad2deg(r1.ravel()) )/cdelt1
for j in xrange(4):
print 'started Stokes: %d' %j
for iter in xrange(0 + j, 16, 4):
outpt = np.zeros(shape = npix, dtype=np.float64)
#by = outpt.copy()
# mask beam
bm_data = beam_data[iter]
#masked_beam= beam_data[iter]
shape = bm_data.shape
rad = np.linspace(-shape[0]/2,shape[-1]/2,shape[0])
rad2d = np.sqrt(rad[np.newaxis,:]**2+rad[:,np.newaxis]**2)
mask = rad2d <= radius/abs(cdelt2)
masked_beam = bm_data*mask
s1 = ndimage.map_coordinates(masked_beam, [xc, yc], mode = 'constant')
bm_map = s1.reshape(dist.shape[0], dist.shape[-1])
for itr in xrange(npix):
g_xy = (1.0/(np.sqrt(2*np.pi)*np.std(dist[itr])))*np.exp(-(dist[itr])**2/(2*np.var(dist[itr])))
#weighted_healpix_map = np.convolve(healpix_map[j, ...][ipix_indx[itr]], g_xy/g_xy.sum(), mode='same')
weighted_healpix_map = ndimage.filters.convolve(healpix_map[j, ...][ipix_indx[itr]], g_xy/g_xy.sum(), mode='reflect')
#outpt[itr] = np.sum(weighted_healpix_map*(bm_map[itr]/bm_map[itr].sum()))
outpt[itr] = np.sum(weighted_healpix_map*(bm_map[itr]))
#print 'itr', itr
alpha = file('pap%d.save'%iter, 'wb')
#h_map = ndimage.filters.gaussian_filter(outpt, sigma = 3.)
cPickle.dump(outpt, alpha, protocol = cPickle.HIGHEST_PROTOCOL)
alpha.close()
print 'Just dumped stripp%d.save:-------'%iter
print 'Loading dumped files:-------'
loaded_objects = []
for itr4 in xrange(16):
alpha = file('stripp%d.save'%itr4, 'rb')
loaded_objects.append(cPickle.load(alpha))
alpha.close()
measured_map.append(copy.deepcopy(loaded_objects))
return measured_map
Remember that HEALPix maps can be in either "Ring" or "Nested" format. It sounds like you may need to add the keyword nest=True to your healpy functions like hp.pix2ang. If your input maps are in nested format, this keyword is needed.
For example:
I recently tried using the healpy.smoothing() function, and found my resulting image to have rings (perhaps like you described), upon viewing the output map with healpix.mollview(). The rings disappeared and the image was presented as I expected, after running mollview with the nested=True keyword. Check what ordering schemes your input files use
Reference:
http://healpy.readthedocs.org/en/latest/tutorial.html#creating-and-manipulating-maps
Healpix supports two different ordering schemes, RING or NESTED. By
default, healpy maps are in RING ordering. In order to work with
NESTED ordering, all map related functions support the nest keyword,
for example: hp.mollview(m, nest=True, title="Mollview image NESTED")

Categories

Resources