How to represent density information on a matplotlib 3-D scatter plot - python-3.x

I am trying to plot the r,g,b channels in an image as a 3-D scatter plot.
This works well when i have a black and white image as i get a scatter plot with just two distinct clusters at two ends of the scatter plot.
However for color images the scatter plot does not make much sense visually because there are r,g,b values corresponding to many points in the color space in the image.
So i end up with something like the image shown below -
What i would like to achieve is somehow represent density information. For example if the number of points corresponding to (255,255,255) are 1000 and the number of points corresponding to (0,0,0) are only 500 then i want (255,255,255) to be dark red and (0,0,0) to be yellow/orangish
How do i achieve this in matplotlib? I am okay with some sort of bubble effect as well where the (255,255,255) is represented as a bigger bubble compared to (0,0,0) although i feel density information encoded as color information would be more visually appealing

Here's an attempt using Gaussian KDE. It's still far from perfect and the result largely depends on the estimation parameters (bw_method). There is perhaps a simpler way, maybe something using np.unique to get the frequency of each unique colour.
The idea is to estimate color density distribution as a multivariate gaussian mixture and use that as a colormap for the scatter plot.
It's a bit slow for anything serious but I think it gives nice results with small enough images. Maybe some FFT+convolution based estimation method could be faster.
Let's see some code. Nothing fancy: it flattens and reshapes image data the way gaussian_kde likes it and return RGB and density components. You can play with bw_method and see how the results change, the bigger, the smoother density you'll get.
from scipy.stats import gaussian_kde
def img_to_rgbk(img, bw=0.1):
rgb = img.reshape(-1, 3).T
k = gaussian_kde(rgb, bw_method=bw)(rgb)
r, g, b = rgb
return r, g, b, k
Here's the results with a toy image
img = chelsea()[100:200, 100:200]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
r, g, b, k = img_to_rgbk(img, bw=0.5)
ax.scatter(r, g, b, c=k, alpha=0.2)
Notice c=k is used to set map marker color to the density information, alpha is needed to see a bit through the cloud.
Chelsea
Random colors
Gradient
Note here you can see how the wrong choice of bandwidth can be misleading. A small enough bw_method should reveal essentially a single color per column, repeated along rows. So every dot should have the same color (and it will with the right bandwidth).
Gradient + noise
Here with a better bandwidth and some noise to spread the colors. Notice the bigger density around the white-ish area where the discontinuity in the no-noise plot becomes a density maximum.

Related

Is it possible to smoothly change (like a photoshop gradient) the color of a plotted curve as a function of distance from a given point?

Suppose one has a plot like this, for which the peak is at (x,y) = (0,0.40). The distribution is plotted in blue. Is it possible to edit the color scheme of the distribution plot in such a way that the color is a gradient - the farther from x (or y or independently for both) the more the color changes - like this?
I've searched SO for help with this, but only found solutions in which line segments were different colors. But, I want the color transition to be smooth (like this but not 3-D) instead of rough, and I want the color to depend on its distance from a particular value rather than pre-determined "randomly". A different SO post did something similar (not quite what I want though), but could only do so as a scatter plot, which only works for changing colors based on x-value if the peak is at x=0 - I'd prefer it be generalized. As an example, the further from x=0 the redder the curve gets. Ideally, there's a way to do this with a matplotlib colormap.

How to generate color palette based on given image?

I would like to generate color palette based on the given image containig max. 10 colors. Assume that, the given picture is bot bigger then 800x600 px. I've tried the next algorithm:
Generate 500 random X, Y values.
Check the colors' R,G,B values at the (X,Y) position, put colors into an array.
Find similar colors to each color, count how many similar colors have found. (Similar means: +- 10 difference in R, G, B)
Display colors which have the most similar colors.
The result is not what I expect. Any idea how to get the appropriate colors?
An example, I want something like this
You probably want Median Cut or K-means.
With median cut, you'll generate a point cloud of color samples from your source image. Divide the pointcloud in half at its median across the axis with maximum variance, creating two sub-pointclouds. Recursively divide these until you have the desired number of leaf nodes. You can then generate a palette by averaging the color samples in each leaf node.
With K-means, you select k random color samples from your image. These will be the first color samples in k buckets. Then, for each pixel, add its color value to the bucket whose average color is closest to that of the pixel in question-- you may use euclidean distance to determine "closeness". After all pixels have been sampled, the average colors of the k buckets is your palette.
You will get better results if you first convert your color samples to CIE lab color space, where euclidean distance is a better measure of perceptual distance.

how to choose a range for filtering points by RGB color?

I have an image and I am picking colors by RGB (data sampling). I select N points from a specific region in the image which has the "same" color. By "same" I mean, that part of the image belongs to an object, (let's say a yellow object). Each picked point in the RGB case has three values [R,G,B]. For example: [120,150,225]. And the maximum and minimum for each field are 255 and 0 respectively.
Let's assume that I picked N points from the region of the object in the image. The points obviously have different RGB values but from the same family (a gradient of the specific color).
Question:
I want to find a range for each RGB field that when I apply a color filter on the image the pixels related to that specific object remain (to be considered as inliers). Is it correct to find the maximum and minimum from the sampled points and consider them as the filter range? For example if the max and min of the field R are 120 ,170 respectively, can it be used as a the range that should be kept.
In my opinion, the idea is not true. Because when choosing the max and min of a set of sampled data some points will be out of that range and also there will be some point on the object that doesn't fit in this range.
What is a better solution to include more points as inliers?
If anybody needs to see collected data samples, please let me know.
I am not sure I fully grasp what you are asking for, but in my opinion filtering in RGB is not the way to go. You should use a different color space than RGB if you want to compare pixels of similar color. RGB is good for representing colors on a screen, but you actually want to look at the hue, saturation and intensity (lightness, or luminance) for analysing visible similarities in colors.
For example, you should convert your pixels to HSI or HSL color space first, then compare the different parameters you get. At that point, it is more natural to compare the resulting hue in a hue range, saturation in a saturation range, and so on.
Go here for further information on how to convert to and from RGB.
What happens here is that you implicitly try to reinvent either color indexing or histogram back-projection. You call it color filter but it is better to focus on probabilities than on colors and color spaces. Colors of course not super reliable and change with lighting (though hue tends to stay the same given non-colored illumination) that's why some color spaces are better than others. You can handle this separately but it seems that you are more interested in the principles of calculating "filtering operation" that will do segmentation of the foreground object from background. Hopefully.
In short, a histogram back-projection works by first creating a histogram for R, G, B within object area and then back-projecting them into the image in the following way. For each pixel in the image find its bin in the histogram, calculate its relative weight (probability) given overall sum of the bins and put this probability into the image. In such a way each pixel would have probability that it belongs to the object. You can improve it by dividing with probability of background if you want to model background too.
The result will be messy but somewhat resemble an object segment plus some background noise. It has to be cleaned and then reconnected into object using separate methods such as connected components, grab cut, morphological operation, blur, etc.

WebGL color mix calculation

What is WebGL color mix calculation algorithm? I need to draw quadrangle with 4-way gradient color fill and I decided to do it with 3-way gradient triangles (like this), calculating the center of quadrangle and using such point for 4 triangles to get the best result of gradient smoothness. To do it right, I need to calculate the color of the center of quadrangle by same way as WebGL calculates color mix for 3-way gradient fill. What is the formular for such calculation?
WebGL uses linear interpolation for vertex attributes. The formula for interpolating a value across a square given samples at the four corners is simply linear interpolation applied twice. In GLSL,
mix(mix(color00, color01, y), mix(color10, color11, y), x)
If you are interested in the center point in particular, this is just
0.25 * (color00 + color01 + color10 + color11)
However, if your goal is to interpolate the four colors smoothly across a square, in a WebGL application, then you don't actually need to perform this calculation yourself, and you don't need to use four triangles!
Create a 2×2 texture with your four colors.
Set its TEXTURE_MAG_FILTER to LINEAR.
Draw your square with that texture applied in the usual fashion, but with texture coordinates ranging from 0.25 to 0.75.
This performs the same interpolation you're looking for, but using built-in facilities. If you wanted, you could also skip using a texture, but still have “texture” coordinates, and use the mix formula above to map the coordinates to your four colors.
The reason this works is that texture coordinates, unlike arbitrary colors, are such that linearly interpolating between 3 points gives you non-degenerate results which you can then use to lookup the color taking into consideration all 4 color values.

Should the result of sRGB->CIEXYZ->discard luminance be convertible back to sRGB?

I'm writing shadow removal for my pony detector. After I've converted a PNG image from sRGB to CIE XYZ I remove the luminance as per instructions:
When I try to convert the image back to sRGB for display, I get RGB values that fall outside the sRGB gamut (I get values greater than 255). Is this normal, or should I keep looking for bugs? Note: conversion to XYZ and back without modification produces no glitches.
Illustration (top left: original, bottom left: byte value wraparaund for red and blue hues):
For completeness: top right: color ratios, bottom right: convert to HSV and equalize value.
The final transformation does not remove the luminance, it creates two new values, x, y that together define the chromacity while Y contains the luminance. This is the key paragraph in your instructions link (just before the formulas you link):
The CIE XYZ color space was
deliberately designed so that the Y
parameter was a measure of the
brightness or luminance of a color.
The chromaticity of a color was then
specified by the two derived
parameters x and y, two of the three
normalized values which are functions
of all three tristimulus values X, Y,
and Z:
What this means is that if you have an image of a surface that has a single colour, but a part of the surface is in the shadow, then in the xyY space the x and y values should be the same (or very similar) for all pixels on the surface whether they are in the shadow or not.
The xyz values you get from the final transformation cannot be translated directly back to RGB as if they were XYZ values (note capitalization). So to answer your actual question: If you are using the xyz values as if they are XYZ values then there are no bugs in your code. Translation to RGB from that should not work using the formulas you linked.
Now if you want to actually remove the shadows from the entire image what you do is:
scale your RGB values to be floating point valies in the [0-1] range by dividing each value by 255 (assuming 24-bit RGB). The conversion to floating point helps accuracy a lot!
Convert the image to xyY
Replace all Y values with one value, say 0.5, but experiment.
Reverse the transformation, from xyY to XYZ:
then to RGB:
Then rescale your RGB values on the [0..255] interval.
This should give you a very boring but shadowless version of your original image. Of course if your goal is detecting single colour regions you could also just do this on the xy values in the xyY image and use the regions you detect there on the original.

Resources