Gray matpotlib figure face color turns black when saving the figure as .eps - matplotlib

I am using the following code to get this:
1
However, when saved as .eps, the face color of the figure turns from gray ( what I want) to black. See this:
2
Any reason why?
dim = np.arange(1, 32, 1)
fig, ax = plt.subplots(figsize=(7,9))
heatmap = ax.imshow(h.T, cmap=plt.cm.get_cmap('Blues', 4), clim=[1,144])
cbaxes = fig.add_axes([.8, .35, .04, .3])
cbar = fig.colorbar(heatmap, ticks = [1, 36, 72, 108, 144], label = 'Number of valid records per day', cax = cbaxes)
ax.set_ylabel("Days", fontsize=15)
ax.set_xlabel("Months", fontsize=15)
ax.set_title("Number of valid records per day", fontsize=20)
ax.set_yticks(range(0,31))
ax.set_yticklabels(dim, ha='center', minor=False, fontsize=12)
ax.set_xticks(range(0,13,1))
ax.set_xticklabels(ylabel[7:20], rotation = 45, ha = 'right')
ax.set_facecolor('gray')
cbar.set_label('Number of valid records')
ax.xaxis.set_minor_locator(MultipleLocator(0.5))
ax.yaxis.set_minor_locator(MultipleLocator(0.5))
ax.tick_params(axis='y', which='major', pad=10)
ax.grid(which = 'minor', color = 'w')
fig.show()
plt.savefig("drive/My Drive/fig.eps")

Your problem is probably caused by your 'gray' colour actually being black with transparency (which is the a channel in rgba), i.e. the rgba color (0,0,0,.5), which is black with a 50% transparency. If you ran your code interactively, you should have gotten a message:
"The PostScript backend does not support transparency; partially
transparent artists will be rendered opaque."
.eps (Postscript in the message refers to encapsulated postscript) doesn't support transparency, I imagine under the hood when your figure is being saved as .eps it simply takes the color channels i.e. the rgb values, which is equivalent to setting alpha to 1.
You can either:
Rasterize your figure/axes before saving as .eps using fig.set_rasterized(True)/ax.set_rasterized(True),
Save in a different format which supports transparency (.pdf, .png etc.)
Choose a color/cmap(colormap) which does not include transparency.
To expand #3 for your specific option, if it's the case that missing values aren't plotted and we're seeing the axes facecolor, you can try explicitly specifying an rgb color ax.set_facecolor(.5,.5,.5) instead of ax.set_facecolor('grey').

Related

Problem with Removing Vertical Lines using tricontour

I am plotting zero-level contours using tricontour (do not want to have to triangulate data) and I am having trouble with getting rid of the vertical lines that occur when the y data goes to the next column of my grid and the sign of my z-value changes. I have tried using mask, nan, and removing the data points for which this occurs, with no luck.
Here is the code that I am using
plt.title(case+ r" case: $\alpha={}$ - Zero-level Contour Plots of Re$(|M(\gamma)|)$ and Im$|M(\gamma)|$".format(alp_val), fontsize = 16)
plt.tricontour(xx,yy,redett, levels=[0], colors='red')
plt.tricontour(xx,yy,imdett, levels=[0], colors = 'blue', linestyles='dashed')
plt.xlabel(r"x", fontsize=24)
plt.ylabel(r"$y$", fontsize=24)
plt.legend(custom_lines, [r"Re$(|M(\gamma)|)$", r"Im$(|M(\gamma)|)$"])
plt.scatter(0,0, s = 45, color='white', edgecolor='black', label =r'Simple Eigenvalue $\gamma=0$')
plt.scatter(zeroes,np.zeros(len(zeroes)), s = 45, color = 'green', edgecolor='black', label = r'Zero of $\mathcal{E}(\gamma)$')
plt.grid()
plt.show()
Want to remove vertical lines on this

changing the spacing between tick labels on Matplotlib Heatmap

I am using the following code to generate this heatmap:
h= np.vstack((aug2014, sep2014,oct2014, nov2014, dec2014, jan2015, feb2015, mar2015, apr2015, may2015, jun2015, jul2015, aug2015))
dim = np.arange(1, 32, 1)
fig, ax = plt.subplots(figsize=(9,3))
heatmap = ax.imshow(h, cmap=plt.cm.get_cmap('Blues', 4), aspect=0.5, clim=[1,144])
cbar = fig.colorbar(heatmap, ticks = [1, 36, 72, 108, 144], label = 'Number of valid records per day')
ax.set_xlabel("Days", fontsize=15)
ax.set_ylabel("Months", fontsize=15)
ax.set_title("Number of valid records per day", fontsize=20)
ax.set_xticks(range(0,31))
ax.set_xticklabels(dim, rotation=45, ha='center', minor=False)
ax.set_yticks(range(0,13,1))
ax.set_yticklabels(ylabel[7:20])
ax.grid(which = 'minor', color = 'w')
ax.set_facecolor('gray')
fig.show()
As you can see, the labels on the y-axis are not very readable. I was wondering whether there would be a way for me to either increase the dimension of the grid cell or change the scale on the axis to increase the space between the labels. I have tried changing the figsize but all it did was to make the colorbar much bigger than the heatmap. I also have have two subsidiary questions:
Can someone explain to me why the grid lines do not show on the figure although I have defined them?
How can I increase the font of the colorbar title?
Any help would be welcomed!

Is there a way to extract the pixel co-ordinates of a plotted line in matplotlib

Similar to in this StackOverflow post, I understand that it is possible to extract the pixel co-ordinates from points plotted in a pyplot figure.
How to get pixel coordinates for Matplotlib-generated scatterplot?
However, what if we plotted a line between each of those points and wanted to get the location of all the pixels of not just those plotted dots, but all pixels that make up the line.
Is this something that is possible with matplotlib?
A line isn't made up of pixels. The pixels in its trajectory are modified taking line width and antialiasing into account. Drawing a line with default settings and zooming in on the image looks like the image below. Very few pixels get the full 100% of the given color. Lots of pixels are changed.
Depending on your final goal, you could calculate pixel coordinates using the method described in the post you linked (note that the pixels on a saved image can deviate a bit from the pixels on-screen). And then use e.g. Bresenham's line algorithm to find the coordinates of points in-between. Note that a naive Bresenham's algorithm would draw a 45 degree line much thinner looking than a horizontal line. On a modern screen a one-pixel wide line would be almost invisible.
Here is a possible Bresenham-like interpretation of the linked code:
import numpy as np
import matplotlib.pyplot as plt
def points_in_line(x0, y0, x1, y1):
dx = np.round(np.abs(x1 - x0))
dy = np.round(np.abs(y1 - y0))
steps = int(np.round(max(dx, dy))) + 1
return np.vstack([np.linspace(x0, x1, steps), np.linspace(y0, y1, steps)]).T
fig, ax = plt.subplots()
points, = ax.plot([0, 1, 2, 4, 5, 6, 9], [0, 5, 3, 2, 2, 9, 8], 'b-')
ax.axis([-1, 10, -1, 10])
# Get the x and y data and transform them into pixel coordinates
x, y = points.get_data()
xy_pixels = ax.transData.transform(np.vstack([x, y]).T)
x_pix, y_pix = xy_pixels.T
# find all points in each line
all_pix = [points_in_line(x0, y0, x1, y1) for x0, y0, x1, y1 in zip(x_pix[:-1], y_pix[:-1], x_pix[1:], y_pix[1:])]
all_x_pix, all_y_pix = np.concatenate(all_pix).T
# In matplotlib, 0,0 is the lower left corner, whereas it's usually the upper
# left for most image software, so we'll flip the y-coords...
width, height = fig.canvas.get_width_height()
all_y_pix = height - all_y_pix
print('Coordinates of the lines in pixel coordinates...')
for xp, yp in zip(all_x_pix, all_y_pix):
print(f'{x:0.2f}\t{y:0.2f}')
# save the figure with its current DPI
fig.savefig('test.png', dpi=fig.dpi)

matplotlib pyplot imshow tight spacing between images

I have some numpy image arrays, all of the same shape (say (64, 64, 3)). I want to plot them in a grid using pyplot.subplot(), but when I do, I get unwanted spacing between images, even when I use pyplot.subplots_adjust(hspace=0, wspace=0). Below is an example piece of code.
from matplotlib import pyplot
import numpy
def create_dummy_images():
"""
Creates images, each of shape (64, 64, 3) and of dtype 8-bit unsigned integer.
:return: 4 images in a list.
"""
saturated_channel = numpy.ones((64, 64), dtype=numpy.uint8) * 255
zero_channel = numpy.zeros((64, 64), dtype=numpy.uint8)
red = numpy.array([saturated_channel, zero_channel, zero_channel]).transpose(1, 2, 0)
green = numpy.array([zero_channel, saturated_channel, zero_channel]).transpose(1, 2, 0)
blue = numpy.array([zero_channel, zero_channel, saturated_channel]).transpose(1, 2, 0)
random = numpy.random.randint(0, 256, (64, 64, 3))
return [red, green, blue, random]
if __name__ == "__main__":
images = create_dummy_images()
for i, image in enumerate(images):
pyplot.subplot(2, 2, i + 1)
pyplot.axis("off")
pyplot.imshow(image)
pyplot.subplots_adjust(hspace=0, wspace=0)
pyplot.show()
Below is the output.
As you can see, there is unwanted vertical space between those images. One way of circumventing this problem is to carefully hand-pick the right size for the figure, for example I use matplotlib.rcParams['figure.figsize'] = (_, _) in Jupyter Notebook. However, the number of images I usually want to plot varies between each time I plot them, and hand-picking the right figure size each time is extremely inconvenient (especially because I can't work out exactly what the size means in Matplotlib). So, is there a way that Matplotlib can automatically work out what size the figure should be, given my requirement that all my (64 x 64) images need to be flush next to each other? (Or, for that matter, a specified distance next to each other?)
NOTE: correct answer is reported in the update below the original answer.
Create your subplots first, then plot in them.
I did it on one line here for simplicity sake
images = create_dummy_images()
fig, axs = pyplot.subplots(nrows=1, ncols=4, gridspec_kw={'wspace':0, 'hspace':0},
squeeze=True)
for i, image in enumerate(images):
axs[i].axis("off")
axs[i].imshow(image)
UPDATE:
Nevermind, the problem was not with your subplot definition, but with imshow() which distorts your axes after you've set them up correctly.
The solution is to use aspect='auto' in the call to imshow() so that the pictures fills the axes without changing them. If you want to have square axes, you need to create a picture with the appropriate width/height ratio:
pyplot.figure(figsize=(5,5))
images = create_dummy_images()
for i, image in enumerate(images):
pyplot.subplot(2, 2, i + 1)
pyplot.axis("off")
pyplot.imshow(image, aspect='auto')
pyplot.subplots_adjust(hspace=0, wspace=0)
pyplot.show()

small scatter plot markers in matplotlib are always black

I'm trying to use matplotlib to make a scatter plot with very small gray points. Because of the point density, the points need to be small. The problem is that the scatter() function's markers seem to have both a line and a fill. When the markers are small, only the line is visible, not the fill, and the line isn't the right colour (it's always black).
I can get exactly what I want using gnuplot: plot 'nodes' with points pt 0 lc rgb 'gray'
How can I make very small gray points using matplotlib scatterplot()?
scatter([1,2,3], [2,4,5], s=1, facecolor='0.5', lw = 0)
This sets the markersize to 1 (s=1), the facecolor to gray (facecolor='0.5'), and the linewidth to 0 (lw=0).
If the marker has no face (cannot be filled, e.g. '+','x'), then the edgecolor has to be set instead of c, and lw should not be 0:
scatter([1,2,3], [2,4,5], marker='+', edgecolor='r')
The following will no work
scatter([1,2,3], [2,4,5], s=1, marker='+', facecolor='0.5', lw = 0)
because the edge/line will not be displayed, so nothing will be displayed.
The absolute simplest answer to your question is: use the color parameter instead of the c parameter to set the color of the whole marker.
It's easy to see the difference when you compare the results:
from matplotlib import pyplot as plt
plt.scatter([1,2,3], [3,1,2], c='0.8') # marker not all gray
plt.scatter([1,2,3], [3,1,2], color='0.8') # marker all gray
Details:
For your simple use case where you just want to make your whole marker be the same shade of gray color, you really shouldn't have to worry about things like face color vs edge color, and whether your marker is defined as all edges or some edges and some fill. Instead, just use the color parameter and know that your whole marker will be set to the single color that you specify!
In response to zwol's question in comment - my reputation is not high enough to leave comments, so this will have to do: In the event that your colors come from a colormap (i.e., are from a "sequence of values to be mapped") you can use color = as demonstrated in the following:
from matplotlib import pyplot
x = [1,5,8,9,5]
y = [4,2,4,7,9]
numSides = [2,3,1,1,5]
cmap = pyplot.cm.get_cmap("copper_r")
min, max = min(numSides), max(numSides)
for i in range(len(x)):
if numSides[i] >= 2:
cax = pyplot.scatter(x[i], y[i], marker = '+', s = 100, c = numSides[i], cmap = cmap)
cax.set_clim(min, max)
elif numSides[i] == 1:
pyplot.scatter(x[i], y[i], marker = '.', s = 40, color = cmap(numSides[i]))
fig = pyplot.gcf()
fig.set_size_inches(8.4, 6)
fig.savefig('figure_test.png', dpi = 200)
pyplot.show()