I'm using Python and Scipy to perform some basic image manipulation. I've made the image greyscale and subtracted it from a gaussian blur of itself as a form of edge detection. Now I'd like to make it look pretty when running the .imshow() command. If I use one of the default colormaps, for instance,
matplotlib.pyplot.imshow(lena, cmap='binary')
where lena is a matrix representing my image in question, the image appears washed out with a grey background. It looks quite a lot like this.
I would like the image to appear sharper, with only two real colors, white and black, and very little (or no) grey in between.
Since none of the other default colormaps in SciPy can do this, I figured I should make my own. But I'm afraid I don't fully grasp the documentation provided by scipy.
So let's say I have the colormap from the tutorial:
cdict = {'red': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.7),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.0),
(1.0, 1.0, 1.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.0),
(1.0, 0.5, 1.0))}
my_cmap = matplotlib.colors.LinearSegmentedColormap('my_colormap', cdict, 256)
matplotlib.pyplot.imshow(lena, cmap=my_cmap)
How should this look if I want the colormap to be exclusively white from range 0 to .5, and exclusively black from range .5 to 1? Thanks to everyone for the help!
I would expect something like this:
cdict = {'red': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 1.0, 1.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 1.0, 1.0))}
See also: http://matplotlib.org/examples/pylab_examples/custom_cmap.html
Please excuse me if I'm wrong. I never created jumps like the one you want.
Related
I'm trying to make a heatmap over time, but I think matplotlib is messing with the plot colours.
My code is based on the heat equation, I think the specs are not important, the main thing is that I am creating a 3D array and plotting a slice from that array (a 2D matrix), setting which slice I plot using the matplotlib widget Slider.
The important part of the code is this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.colors import LogNorm
def update(val):
newdata = mat[:,:,int(val)]
plot.set_data(newdata)
plt.title(f'{val}')
plt.draw()
def init_plot():
global plot
fig, ax = plt.subplots()
flukacolours = [(1.0, 1.0, 1.0), (0.9, 0.6, 0.9), (1.0, 0.4, 1.0), (0.9, 0.0, 1.0), (0.7, 0.0, 1.0), (0.5, 0.0, 0.8), (0.0, 0.0, 0.8),
(0.0, 0.0, 1.0), (0.0, 0.6, 1.0), (0.0, 0.8, 1.0), (0.0, 0.7, 0.5), (0.0, 0.9, 0.2), (0.5, 1.0, 0.0), (0.8, 1.0, 0.0),
(1.0, 1.0, 0.0), (1.0, 0.8, 0.0), (1.0, 0.5, 0.0), (1.0, 0.0, 0.0), (0.8, 0.0, 0.0), (0.6, 0.0, 0.0), (0.0, 0.0, 0.0)]
cmap_name = 'fluka'
cm = colors.LinearSegmentedColormap.from_list(cmap_name, flukacolours, N=30)
plot = plt.imshow(mat[:,:,0], cmap=cm, norm=LogNorm(vmin=mat.min(), vmax=mat.max()), aspect='auto')
ax = plot.axes
cbar = plt.colorbar(plot, ax=ax)
plt.subplots_adjust(left=0.10, bottom=0.15, right=1, top=0.9)
axfreq = plt.axes([0.10, 0.02, 0.8, 0.03])
freq_slider = Slider(ax=axfreq, label='Slice', valmin=0, valmax=mat.shape[2], valinit=0, valstep=1, orientation='horizontal')
freq_slider.on_changed(update)
plt.show()
if __name__ == "__main__":
mat = crazy_function() # This function returns a 3D np.array
init_plot()
The problem is seen in some slices of the plot, where the colours just... break. In the images below I am showing the differences between 3 consecutive slices. At this point, I thought the problem was in my crazy_function(), but then I noticed the graph value that appears in the upper right corner when you place the cursor inside the chart.
Trying to place the cursor at the same maximum point for each plot, the 36th slice is showing a green tint, which would mean a value in the order 10⁻¹⁶ (as shown in colorbar), but the cursor value shows 7x10⁻⁸, which is the right value of the array that matplotlib is not showing correctly.
.
I think the problem might be my custom colour scale, or more likely the absurdly large scale of the colorbar. Because changing the scale vmin and vmax in the plt.imshow, the colour break tends to decrease and even stop. Which is not a problem, I even prefer a shorter scale to visualize the data, but I was really curious about the cause of this problem.
If you know the answer, I'd love to know. In case it matters, my current version of matplotlib is 3.5.1.
I would like to create my own custom colour map in python, I looked into some of the online examples and found out the commands
from matplotlib import cm
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
cdict1 = {'red': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.1),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 1.0),
(0.5, 0.1, 0.0),
(1.0, 0.0, 0.0))
}
blue_red1 = LinearSegmentedColormap('BlueRed1', cdict1)
plt.imshow(big,interpolation='nearest', cmap=blue_red1, aspect='auto')
plt.colorbar()
plt.show()
With the above command I get a colour map which is (Red - Black - Blue), Where red being maximum and blue being minimum. I would like to create a colour map which is (Black - White - Black). Could someone tell me what should be done or any other method ?
For what it's worth, there's also a simpler method.
The full form of LinearSegmentedColormap gives you the ability to have "hard" stops and gradients in the same colormap, so it's necessarily complex. However, there's a convenience constructor for simple cases such as what you describe.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list('mycmap', ['black', 'white', 'black'])
fig, ax = plt.subplots()
im = ax.imshow(np.random.random((10, 10)), cmap=cmap, interpolation='nearest')
fig.colorbar(im)
plt.show()
You want all three components to be 0 at both 0 and 1, and all three to be 1 at 0.5.
So, you have:
cdict1 = {'red': ((0.0, 0.0, 0.0), # <- at 0.0, the red component is 0
(0.5, 1.0, 1.0), # <- at 0.5, the red component is 1
(1.0, 0.0, 0.0)), # <- at 1.0, the red component is 0
'green': ((0.0, 0.0, 0.0), # <- etc.
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0))
}
I've also found colormap creation confusing. The LinearSegmentedColormap is nice because it is very flexible, but cdict takes some getting used to.
The first - and perhaps most important - thing to making colormaps this way is that you understand RGB colors. Basically, each color has an intensity value from 0 to 1, and higher values give more of that color. In the RGB colorspace, white is represented by all three colors being 1, and black is all three colors 0.
The second thing that is important to learning to make colormaps this way is this: always make the 2nd and 3rd values of each tuple the same until you get comfortable with creating simple linear colormaps. Eventually you can change those values to make discontinuities in the colormaps, but it will only confuse you as you get started.
OK, so the first value in each tuple is the 'fraction' of the colormap and these must go from 0 to 1, the second and third values are the intensity for that color (lower and upper bounds). So, to make a colormap that is 'black-white-black', you would do:
cdict1 = {
'red': ((0.0, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'green': ((0.0, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0)),
}
black_white_black = LinearSegmentedColormap('BlackWhiteBlack', cdict1)
For example,
plt.imshow(np.arange(100).reshape(10,10), cmap=black_white_black, aspect='auto')
plt.colorbar()
Good Luck!
Try a cdict1 of
cdict1 = {'red': ((0.0, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'green': ((0.0, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 1.0, 1.0),
(1.0, 0.0, 0.0))
}
This dictionary describes how colors are interpolated, looking at each red-green-blue component individually. For each component you give it a list of 3-tuples (x, y0, y1) that specify how to interpolate that component, and each value you want is interpolated between two points in the list.
In this case we want to start at black [RGB=(0,0,0)], increase to white [RGB=1,1,1] at the halfway point of the data range, and then decrease back to black at the end.
For each value to assign a color, the map will first convert that value to a fraction of the input range so that it has something in the range [0, 1]. To get the level of the red component, the map will scan the first element in each 3-tuple in the 'red' list and grab the largest one not exceeding your fraction. The red level assigned will be interpolated between the y1 element of that 3-tuple and the y0 element of the next 3-tuple, based on the difference in x value.
And similarly for the blue and green components.
I have a simple PyOpenGL program that draws a cube. I am attempting to draw a transparent subsurface (window) on the front face of the cube. I can get both surfaces to show up, but not in the way that represents a window.
Here is my code for drawing the cube:
# Draws the six faces of a cube
def DrawCube(self):
# Front face - Blue
glPushName(1)
glBegin(GL_QUADS)
glColor4f(0.0, 0.0, 1.0, 1.0)
glNormal3f(0.0, 0.0, 1.0) # Allows for light to reflect off certain parts of surface
glVertex3f(1.0, 0.0, 0.0)
glVertex3f(1.0, 1.0, 0.0)
glVertex3f(1.0, 1.0, 1.0)
glVertex3f(1.0, 0.0, 1.0)
glEnd()
# Back face - Green
glPushName(2)
glBegin(GL_QUADS)
glColor4f(0.0, 1.0, 0.0, 1.0)
glNormal3f(0.0, 0.0,-1.0)
glVertex3f(0.0, 0.0, 0.0)
glVertex3f(0.0, 1.0, 0.0)
glVertex3f(0.0, 1.0, 1.0)
glVertex3f(0.0, 0.0, 1.0)
glEnd()
# Left face - Red
glPushName(3)
glBegin(GL_QUADS)
glColor4f(1.0, 0.0, 0.0, 1.0)
glNormal3f(-1.0,0.0, 0.0)
glVertex3f(0.0, 0.0, 0.0)
glVertex3f(1.0, 0.0, 0.0)
glVertex3f(1.0, 0.0, 1.0)
glVertex3f(0.0, 0.0, 1.0)
glEnd()
# Right face - Orange
glPushName(4)
glBegin(GL_QUADS)
glColor4f(1.0, 0.55, 0.0, 1.0)
glNormal3f(1.0, 0.0, 0.0)
glVertex3f(1.0, 1.0, 0.0)
glVertex3f(1.0, 1.0, 1.0)
glVertex3f(0.0, 1.0, 1.0)
glVertex3f(0.0, 1.0, 0.0)
glEnd()
# Top face - White
glPushName(5)
glBegin(GL_QUADS)
glColor4f(1.0, 1.0, 1.0, 1.0)
glNormal3f(0.0, 1.0, 0.0)
glVertex3f(0.0, 0.0, 1.0)
glVertex3f(1.0, 0.0, 1.0)
glVertex3f(1.0, 1.0, 1.0)
glVertex3f(0.0, 1.0, 1.0)
glEnd()
# Bottom face - Yellow
glPushName(6)
glBegin(GL_QUADS)
glColor4f(1.0, 1.0, 0.0, 1.0)
glNormal3f(0.0,-1.0, 0.0)
glVertex3f(0.0, 0.0, 0.0)
glVertex3f(0.0, 1.0, 0.0)
glVertex3f(1.0, 1.0, 0.0)
glVertex3f(1.0, 0.0, 0.0)
glEnd()
# Window on Front (Blue) Face
glPushName(7)
glBegin(GL_QUADS)
glColor4f(0.0, 1.0, 1.0, 0.4)
glNormal3f(0.0, 0.0, 1.0)
glVertex3f(1.0, 0.25, 0.25)
glVertex3f(1.0, 0.75, 0.25)
glVertex3f(1.0, 0.75, 0.75)
glVertex3f(1.0, 0.25, 0.75)
glEnd()
Here is the result of what I'm drawing.
I want to make the window completely transparent so you can see through to the other side of the cube. How do I do this?
You have two problems. First, you need to set up blending so that the opaque geometry shows through the translucent window:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
The artifacts you're seeing is from drawing two coplanar polygons. In an ideal world, they would end up with the same depth value at the same pixel. But because all calculations are done with limited precision, there can be slight differences, and one polygon shows up in front of the other due to rounding errors. This is commonly referred to as "depth fighting" or "z fighting".
To avoid the depth fighting issue, you have to draw your window slightly in front of the cube. You can do that by using slightly larger x-coordinates for your window. OpenGL also has a built-in mechanism for
glPolygonOffset(1.0f, 1.0f);
glEnable(GL_POLYGON_OFFSET_FILL);
You may want to play with the exact parameters to glPolygonOffset() to get the ideal result.
In my application I'm transitioning from R to native Python (scipy + matplotlib) where possible, and one of the biggest tasks was converting from a R heatmap to a matplotlib heatmap. This post guided me with the porting. While most of it was painless, I'm still not convinced on the colormap.
Before showing code, an explanation: in the R code I defined "breaks", i.e. a fixed number of points starting from the lowest value up to 10, and ideally centered on the median value of the data. Its equivalent here would be with numpy.linspace:
# Matrix is a DataFrame object from pandas
import numpy as np
data_min = min(matrix.min(skipna=True))
data_max = max(matrix.max(skipna=True))
median_value = np.median(matrix.median(skipna=True))
range_min = np.linspace(0, median_value, 50)
range_max = np.linspace(median_value, data_max, 50)
breaks = np.concatenate((range_min, range_max))
This gives us 100 points that will be used for coloring. However, I'm not sure on how to do the exact same thing in Python. Currently I have:
def red_black_green():
cdict = {
'red': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.0),
(1.0, 1.0, 1.0)),
'blue': ((0.0, 0.0, 0.0),
(1.0, 0.0, 0.0)),
'green': ((0.0, 0.0, 1.0),
(0.5, 0.0, 0.0),
(1.0, 0.0, 0.0))
}
my_cmap = mpl.colors.LinearSegmentedColormap(
'my_colormap', cdict, 100)
return my_cmap
And further down I do:
# Note: vmin and vmax are the maximum and the minimum of the data
# Adjust the max and min to scale these colors
if vmin > 0:
norm = mpl.colors.Normalize(vmin=0, vmax=vmax / 1.08)
else:
norm = mpl.colors.Normalize(vmin / 2, vmax / 2)
The numbers are totally empirical, that's why I want to change this into something more robust. How can I normalize my color map basing on the median, or do I need normalization at all?
By default, matplotlib will normalise the colormap such that the maximum colormap value will be the maximum of your data. Likewise for the minimum of your data. This means that the median of the colormap (the middle value) will line up with the interpolated median of your data (interpolated if you don't have a data point exactly at the median).
Here's an example:
from numpy.random import rand
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
cdict = {'red': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.0),
(1.0, 1.0, 1.0)),
'blue': ((0.0, 0.0, 0.0),
(1.0, 0.0, 0.0)),
'green': ((0.0, 0.0, 1.0),
(0.5, 0.0, 0.0),
(1.0, 0.0, 0.0))}
cmap = mcolors.LinearSegmentedColormap(
'my_colormap', cdict, 100)
ax = plt.subplot(111)
im = ax.imshow(2*rand(20, 20) + 1.5, cmap=cmap)
plt.colorbar(im)
plt.show()
Notice the middle of the colour bar takes value 2.5. This is the median of the data range: (min + max) / 2 = (1.5+3.5) / 2 = 2.5.
Hope this helps.
I have a 2D array that I'm plotting with imshow and I would like to have costums colors depending on the value of each pixel of my array. I'll explain it with an example.
from pylab import *
from numpy import *
img = ones((5,5))
img[1][1] = 2
imshow(img,interpolation='nearest');colorbar()
If you ran this code you would see a red square in a blue background. The red square corresponds to the pixel [1][1] in img, while the other pixel are colored blue because they have a value of 1. What if I want the red square to be colored with a custom color?
Or more generally, if I have a 2D array like img in the example, how can I color pixel with the same value with a color I can choose.
I have found this page that explains how to generate a custom colorbar but that's not useful: http://www.scipy.org/Cookbook/Matplotlib/Show_colormaps
That link you sent has the following:
But, what if I think those colormaps are ugly? Well, just make your
own using matplotlib.colors.LinearSegmentedColormap. First, create a
script that will map the range (0,1) to values in the RGB spectrum. In
this dictionary, you will have a series of tuples for each color
'red', 'green', and 'blue'. The first elements in each of these color
series needs to be ordered from 0 to 1, with arbitrary spacing
inbetween. Now, consider (0.5, 1.0, 0.7) in the 'red' series below.
This tuple says that at 0.5 in the range from (0,1) , interpolate from
below to 1.0, and above from 0.7. Often, the second two values in each
tuple will be the same, but using diferent values is helpful for
putting breaks in your colormap. This is easier understand than might
sound, as demonstrated by this simple script:
1 from pylab import *
2 cdict = {'red': ((0.0, 0.0, 0.0),
3 (0.5, 1.0, 0.7),
4 (1.0, 1.0, 1.0)),
5 'green': ((0.0, 0.0, 0.0),
6 (0.5, 1.0, 0.0),
7 (1.0, 1.0, 1.0)),
8 'blue': ((0.0, 0.0, 0.0),
9 (0.5, 1.0, 0.0),
10 (1.0, 0.5, 1.0))}
11 my_cmap = matplotlib.colors.LinearSegmentedColormap('my_colormap',cdict,256)
12 pcolor(rand(10,10),cmap=my_cmap)
13 colorbar()
Isn't this exactly what you want?
Here's an example of how to do it with the image you provided:
import matplotlib
from matplotlib import pyplot as plt
from pylab import *
img = ones((5,5))
img[1][1] = 2
cdict = {'red': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.7),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.0),
(1.0, 1.0, 1.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.0),
(1.0, 0.5, 1.0))}
my_cmap = matplotlib.colors.LinearSegmentedColormap('my_colormap',cdict,256)
plt.pcolor(img,cmap=my_cmap)
plt.colorbar()
plt.show()
Also, if you really want to map a number to a colour you can use discrete_cmap as specified in that example you linked to, here's the example method the scipy documentation provides:
def discrete_cmap(N=8):
"""create a colormap with N (N<15) discrete colors and register it"""
# define individual colors as hex values
cpool = [ '#bd2309', '#bbb12d', '#1480fa', '#14fa2f', '#000000',
'#faf214', '#2edfea', '#ea2ec4', '#ea2e40', '#cdcdcd',
'#577a4d', '#2e46c0', '#f59422', '#219774', '#8086d9' ]
cmap3 = col.ListedColormap(cpool[0:N], 'indexed')
cm.register_cmap(cmap=cmap3)