My data is a 100 x 100 array of strings which are all hex codes:
[['#10060e' '#11070f' '#060409' ... '#08030a' '#09030d' '#12050f']
['#110600' '#09010e' '#0d0210' ... '#09030f' '#08060b' '#160a0a']
['#0a070e' '#13060f' '#0c040f' ... '#0c0610' '#0e040c' '#0a020f']
...
['#0c020d' '#09040b' '#10070c' ... '#0a090f' '#160613' '#08000f']
['#0a020f' '#09040a' '#150812' ... '#11040d' '#07040b' '#0b060d']
['#0d0715' '#0e020c' '#140710' ... '#0a0112' '#12090e' '#0c020d']]
Matplotlib: throws this error: TypeError: cannot perform reduce with flexible type
I think the issue it is having is it cannot give a colour to these as they are strings, not numbers. I can only find examples where all the data is numerical and has a colour map applied to it, nothing where every bit of data's colour is specified.
I would like to tell Matplotlib what colour I'd like all of these to be using, surprise surprise, the hex codes. How can I go about doing that?
Full(er) code sample:
z = np.asanyarray(pixel_arr)
x = np.arange(0, width, 1) # len = 100
y = np.arange(0, height, 1) # len = 100
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z)
plt.show()
I would like to tell Matplotlib what colour I'd like all of these to be using
It sounds like you have a bunch of pixel values you want to plot, i.e., an image. So you can treat it like one.
import matplotlib.pyplot as plt
import numpy as np
data = np.array([['#10060e', '#11070f', '#060409', '#08030a', '#09030d', '#12050f'],
['#110600', '#09010e', '#0d0210', '#09030f', '#08060b', '#160a0a'],
['#0a070e', '#13060f', '#0c040f', '#0c0610', '#0e040c', '#0a020f'],
['#0c020d', '#09040b', '#10070c', '#0a090f', '#160613', '#08000f'],
['#0a020f', '#09040a', '#150812', '#11040d', '#07040b', '#0b060d'],
['#0d0715', '#0e020c', '#140710', '#0a0112', '#12090e', '#0c020d']])
img = [[tuple(bytes.fromhex(pixel[1:])) for pixel in row] for row in data]
img = np.array(img, dtype=np.uint8)
plt.imshow(img)
plt.show()
The output may be dark at first glance, but that's because I used the data you showed us, which all happen to be very dark pixels.
Related
I wrote a function with this purpose:
to create a matplotlib figure, but not display it
with no frames, axes, etc.
to plot in the figure an input 2D array using a user-passed colormap
to save the colormapped 2D array from the canvas to a numpy array
that the output array should be the same size as the input
There are lots of questions with answers for tasks similar to either points 1-2 or point 4; for me it was also important to automate point 5. So I started by combining parts from both #joe-kington 's answer and from #matehat 's answer and comments to it, and with small modifications I got to this:
def mk_cmapped_data(data, mpl_cmap_name):
# This is to define figure & ouptput dimensions from input
r, c = data.shape
dpi = 72
w = round(c/dpi, 2)
h = round(r/dpi, 2)
# This part modified from #matehat's SO answer:
# https://stackoverflow.com/a/8218887/1034648
fig = plt.figure(frameon=False)
fig.set_size_inches((w, h))
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
plt.set_cmap(mpl_cmap_name)
ax.imshow(data, aspect='auto', cmap = mpl_cmap_name, interpolation = 'none')
fig.canvas.draw()
# This part is to save the canvas to numpy array
# Adapted rom Joe Kington's SO answer:
# https://stackoverflow.com/a/7821917/1034648
mat = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
mat = mat.reshape(fig.canvas.get_width_height()[::-1] + (3,))
mat = normalise(mat) # this is just using a helper function to normalize output range
plt.close(fig=None)
return mat
The function does what it is supposed to do and is fast enough.
My question is whether I can make it more efficient and or more pythonic in any way.
If you're wanting RGB output that exactly matches the shape of the input array, it's probably easiest to not create a figure, and instead use the colormap objects directly. For example:
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
# Random data with a non 0-1 range.
data = 500 * np.random.random((100, 100)) - 200
# We'll use `LinearSegementedColormap` and `Normalize` instances directly
cmap = plt.get_cmap('viridis')
norm = plt.Normalize(data.min(), data.max())
# The norm instance scales data to a 0-1 range, cmap makes it RGB
rgb = cmap(norm(data))
# MPL uses a 0-1 float RGB representation, so we'll scale to 0-255
rgb = (255 * rgb).astype(np.uint8)
Image.fromarray(rgb).save('test.png')
Note that you likely don't want the additional step of saving it as a PNG, but I wanted to be able to show the result visually. This is exactly a 100x100 image where each pixel corresponds to the original input data.
This is what matplotlib does behind-the-scenes when you call imshow. The data is first run through a Normalize instance to scale it from its original range to 0-1. Then any Colormap instance can be called directly with the 0-1 results to turn the scalar data into RGB data.
One letter variables are hard to understand.
Change:
r -> n_rows
c -> n_cols
w -> width
h -> height
Is there a way to extract the data from an array, which corresponds to a line of a contourplot in python? I.e. I have the following code:
n = 100
x, y = np.mgrid[0:1:n*1j, 0:1:n*1j]
plt.contour(x,y,values)
where values is a 2d array with data (I stored the data in a file but it seems not to be possible to upload it here). The picture below shows the corresponding contourplot. My question is, if it is possible to get exactly the data from values, which corresponds e.g. to the left contourline in the plot?
Worth noting here, since this post was the top hit when I had the same question, that this can be done with scikit-image much more simply than with matplotlib. I'd encourage you to check out skimage.measure.find_contours. A snippet of their example:
from skimage import measure
x, y = np.ogrid[-np.pi:np.pi:100j, -np.pi:np.pi:100j]
r = np.sin(np.exp((np.sin(x)**3 + np.cos(y)**2)))
contours = measure.find_contours(r, 0.8)
which can then be plotted/manipulated as you need. I like this more because you don't have to get into the deep weeds of matplotlib.
plt.contour returns a QuadContourSet. From that, we can access the individual lines using:
cs.collections[0].get_paths()
This returns all the individual paths. To access the actual x, y locations, we need to look at the vertices attribute of each path. The first contour drawn should be accessible using:
X, Y = cs.collections[0].get_paths()[0].vertices.T
See the example below to see how to access any of the given lines. In the example I only access the first one:
import matplotlib.pyplot as plt
import numpy as np
n = 100
x, y = np.mgrid[0:1:n*1j, 0:1:n*1j]
values = x**0.5 * y**0.5
fig1, ax1 = plt.subplots(1)
cs = plt.contour(x, y, values)
lines = []
for line in cs.collections[0].get_paths():
lines.append(line.vertices)
fig1.savefig('contours1.png')
fig2, ax2 = plt.subplots(1)
ax2.plot(lines[0][:, 0], lines[0][:, 1])
fig2.savefig('contours2.png')
contours1.png:
contours2.png:
plt.contour returns a QuadContourSet which holds the data you're after.
See Get coordinates from the contour in matplotlib? (which this question is probably a duplicate of...)
I'm using scipy.stats.binned_statistic_2d and then plotting the output. When I use stat="count", I have no problems. When I use stat="mean" (or np.max() for that matter), I end up with negative values in each bin (as identified by the color bar), which should not be the case because I have constructed zvals such that it is always greater than zero. Does anyone know why this is the case? I've included the minimal code I use to generate the plots. I also get an invalid value RunTime warning, which makes me think that something strange is going on in binned_statistic_2d. The following code should just copy and run.
From the documentation:
'count' : compute the count of points within each bin. This is
identical to an unweighted histogram. `values` array is not
referenced.
which leads me to believe that there might be something going on in binned_statistic_2d and how it handles z-values.
import numbers as _numbers
import numpy as _np
import scipy as _scipy
import matplotlib as _mpl
import types as _types
import scipy.stats
from matplotlib import pyplot as _plt
norm_args = (0, 3, int(1e5)) # loc, scale, size
x = _np.random.random(norm_args[-1]) # xvals can be log scaled.
y = _np.random.normal(*norm_args) #_np.random.random(norm_args[-1]) #
z = _np.abs(_np.random.normal(1e2, *norm_args[1:]))
nbins = 1e2
kwargs = {}
stat = _np.max
fig, ax = _plt.subplots()
binned_stats = _scipy.stats.binned_statistic_2d(x, y, z, stat,
nbins)
H, xedges, yedges, binnumber = binned_stats
Hplot = H
if isinstance(stat, str):
cbar_title = stat.title()
elif isinstance(stat, _types.FunctionType):
cbar_title = stat.__name__.title()
XX, YY = _np.meshgrid(xedges, yedges)
Image = ax.pcolormesh(XX, YY, Hplot.T) #norm=norm,
ax.autoscale(tight=True)
grid_kargs = {'orientation': 'vertical'}
cax, kw = _mpl.colorbar.make_axes_gridspec(ax, **grid_kargs)
cbar = fig.colorbar(Image, cax=cax)
cbar.set_label(cbar_title)
Here's the runtime warning:
/Users/balterma/Library/Enthought/Canopy_64bit/User/lib/python2.7/sitepackages/matplotlib/colors.py:584: RuntimeWarning: invalid value encountered in less cbook._putmask(xa, xa < 0.0, -1)
Image with mean:
Image with max:
Image with count:
Turns out the problem was interfacing with plt.pcolormesh. I had to convert the output array from binned_statistic_2d to a masked array that masked the NaNs.
Here's the question that gave me the answer:
pcolormesh with missing values?
I have three matrices that I'd like to plot, but the only solution I've come up with is just plotting one after the other, and that leaves me with the last matrix plotted.
ax.imshow(mat1, cmap='Blues', interpolation='nearest')
ax.imshow(mat2, cmap='binary', interpolation='nearest')
ax.imshow(mat3, cmap='autumn', interpolation='nearest') # actual plot
What I want is to display all 0s in the three matrices in white, and higher values in different tones depending on the matrix, e.g.: blue, black and red. Also, in that example, red cells would have precedence over black and black over blue. The solution I'm imagining to this is a function that, given a triple (blue, black, red) with the different values for each component, returns the color the cell should be colored, and feed it to a ColorMap, but I really don't know how to do so or if it's even possible.
Every kind of help and even different solutions (that's the most likely to happen) are welcome and appreciated. Thanks in advance.
You want a fourth image, with the RGB value at each point a function of the single value of the first three matrixes at the corresponding point? If so, can you produce the algebra to get from three values to the RGB fourth?
Your question suggests confusion about how plotting turns data into colors. A colormap takes single-valued data, normalizes it, and maps it into some named array of colors. 0 values might be mapped to any color, depending on the colormap and the rest of the data.
A bitmap defines (red, green, blue) values at each pixel. Proper bitmaps have header sections, but the data is an (m,n,3) array. imshow plots just that array; it expects the RGB values to be in the [0,1] range.
If you have three data matrices, you have to choose how to map the values to RGB values. Here's an example with three kinds of mapping to RGB. The first two rows are dummy data with a range of values, shown either with a colormap or as the simplest RGB representation. The last row shows ways of combining all three dummy matrices into one image using the whole colorspace.
# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
#dummy data
x = 8
y = 15
mat = []
mat.append(np.arange(x * y).reshape((x, y)) / float(x * y) )
mat.append(np.arange(x * y).reshape((y, x)).T / float(x* y))
mat.append(np.arange(y) * np.arange(x)[:,np.newaxis] / float(99))
# NOTE: this data is approximately in the RGB range. If yours isn't, normalize,
# here or in your makeRGB function.
# (The colormap normalizes single-valued data).
fig, axs = plt.subplots(figsize=(7,4), nrows=3, ncols=3,
gridspec_kw={'hspace':0.6})
axs[0,0].imshow(mat[0], cmap='Reds', interpolation='nearest')
axs[0,1].imshow(mat[1], cmap='Greens', interpolation='nearest')
axs[0,2].imshow(mat[2], cmap='Blues', interpolation='nearest')
axs[0,0].set_xlabel('Reds Colormap')
axs[0,1].set_xlabel('Greens Colormap')
axs[0,2].set_xlabel('Blues Colormap')
def asOneHue(mat, hue):
"""
Use a single-valued matrix to represent one hue in a RGB file.'
"""
RGBout = np.zeros((len(mat),len(mat[0]),3))
RGBout[:,:,i] = mat
return RGBout
for i in (0,1,2):
axs[1,i].imshow(asOneHue(mat[i],i))
axs[1,0].set_xlabel('Reds bitmap')
axs[1,1].set_xlabel('Greens bitmap')
axs[1,2].set_xlabel('Blues bitmap')
# different ways to combine 3 values
def makeRGB0(mats):
RGBout = np.zeros((len(mats[0]),len(mats[0][0]),3))
#RGBout = np.ones((len(mats[0]),len(mats[0][0]),3))
for i in (0,1,2):
RGBout[:,:,i] = mats[i]
return RGBout
axs[2,0].imshow(makeRGB0(mat))
axs[2,0].set_xlabel('Color layers')
def makeRGB1(mats):
RGBout = np.zeros((len(mats[0]),len(mats[0][0]),3))
i,j,k = RGBout.shape
for x in range(i):
for y in range(j):
RGBout[x,y] = (mats[0][x][y] / 2,
mats[1][x][y],
1 - mats[2][x][y])
return RGBout
axs[2,1].imshow(makeRGB1(mat))
axs[2,1].set_xlabel('Algebraic')
def makeRGB2(mats):
RGBout = np.zeros((len(mats[0]),len(mats[0][0]),3))
i,j,k = RGBout.shape
for x in range(i):
for y in range(j):
if mats[0][x][y] > .8:
RGBout[x,y] = (mats[0][x][y],
0,
0)
elif mats[1][x][y] > .8:
RGBout[x,y] = (0,
mats[1][x][y],
0)
else:
RGBout[x,y] = (mats[0][x][y],
mats[1][x][y],
mats[2][x][y])
return RGBout
axs[2,2].imshow(makeRGB2(mat))
axs[2,2].set_xlabel('If-else')
plt.show()
Is it possible to plot a map of RGB values using Matplotlib?
I have three columns of data that I read from a text file in the following form where x and y are the desired coordinates and z is a hex string of the desired rgb color to plot at the given coordinates:
x y z
1 0.5 #000000
2 0.5 #FF0000
3 0.5 #00FF00
1 1.5 #0000FF
2 1.5 #FFFF00
3 1.5 #00FFFF
1 1.5 #FF00FF
2 2.5 #C0C0C0
3 2.5 #FFFFFF
This is my current state of code. An error is thrown from the griddata() function:
import pandas as pds
import matplotlib.pyplot as plt
# Import text file using pandas
datafile = pds.read_csv(PathToData,sep='\t')
X=datafile.x
Y=datafile.y
Z=datafile.z
# Generate mesh as 'numpy.ndarray' type for plotting
# This throws the following error:
# ValueError: could not convert string to float: #FFAA39
Z=griddata(X, Y, Z, unique(X), unique(Y))
Many thanks
griddata is a function for interpolating unevenly spaced data on to a grid (griddata doc). In your case, it looks like you already have data on a grid, you just need to reshape it. The error you are getting arises because griddata is trying to convert you color hex codes to floats for the interpolation, which you should be getting because there is not a sensible float interpertation of #00FF00.
data_dims = (3, 3) # or what ever your data really is
X = np.asarray(x).reshape(data_dims)
Y = np.asarray(y).reshape(data_dims)
C = np.asarray([mpl.colors.colorConverter.to_rgb(_hz) for _hz in z]).reshape(data_dims + (3,))
# the colors converted to (r, g, b) tuples
The asarray calls are to make sure we have arrays, not data frames.
If you just want to see the array we can use imshow to plot it:
imshow(c, interpolation='none',
origin='bottom',
extent=[np.min(X), np.max(X), np.min(Y), np.max(Y)])
color converter doc, imshow doc.
You might need to transpose/flip/rotate C to get the orientation you expect.