I'm trying to create a cross stitch pattern with python as shown in the attached image.
So far I simply have the pixilated image. I could import it in excel and manually add the grid and colors etc. But how can I 'easily' automate this in python? Can I use any of the normal figure plotting functions (pyplot), or should I look into tkinter?
I'm fairly ok making scripts in python for engineering purposes, but completely new to GUI-stuff.
Ideally my output would be a vectored pdf
from scipy import misc
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker as plticker
arr = misc.imread('Fox_drawing_pixelized.png', mode= 'RGBA') # 640x480x3 array
fig = plt.figure()
imgplot = plt.imshow(arr) # RGBA
ax = plt.gca()
ax.grid(True)
ax.grid(b=True, which='major', color='b', linestyle='-')
plt.minorticks_on()
loc = plticker.MultipleLocator(base=1)
ax.xaxis.set_minor_locator(loc)
ax.yaxis.set_minor_locator(loc)
ax.grid(b=True, which='minor', color='k', linestyle='-',linewidth=.3)
fig.savefig("foo.pdf", bbox_inches='tight')
How do I set the gridlines at 0.5 rather than on the units (in the middle through each pixel)?
How do I plot text throught each pixel, I already have the image in an array with numbers how to plot this on top?
To shift the gridlines, you can simply change the ticks position:
ax.set_xticks(np.arange(-0.5, arr.shape[1], 5))
will put one major tick each 5 pixels starting from the border of the first pixel.
ax.set_xticks(np.arange(-0.5, arr.shape[1], 1), minor=True)
does the same thing but every pixel for minor ticks. And then do the same for y but with arr.shape[0].
To add text, you can simply use ax.text(x, y, 'text'). I used a dictionary to match the colors (in hex format because rgb lists cannot be dictionary keys) to the texts. What you need to pay attention to is that (i, j) matrix indexes correspond to (y, x) coordinates.
Here is the full code:
from scipy import misc
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker as plticker
from matplotlib.colors import to_hex
arr = misc.imread('image.png', mode= 'RGB') # array
# adapt figure size to the image size.
fig = plt.figure(figsize=(0.2*arr.shape[1], 0.2*arr.shape[0]))
imgplot = plt.imshow(arr) # RGB
ax = plt.gca()
ax.grid(True)
ax.grid(b=True, which='major', color='b', linestyle='-')
plt.minorticks_on()
ax.grid(b=True, which='minor', color='k', linestyle='-',linewidth=.3)
# put a major gridline every 5 pixels
ax.set_xticks(np.arange(-0.5, arr.shape[1], 5))
ax.set_yticks(np.arange(-0.5, arr.shape[0], 5))
# set ticks label
ax.set_xticklabels(np.arange(0, arr.shape[1], 5))
ax.set_yticklabels(np.arange(0, arr.shape[0], 5))
# put a minor gridline every pixel
ax.set_xticks(np.arange(-0.5, arr.shape[1], 1), minor=True)
ax.set_yticks(np.arange(-0.5, arr.shape[0], 1), minor=True)
fig.tight_layout(pad=0) # reduce space around image
# display text
colors_to_text = {'#000000': 'B', '#ffffff': 'W', '#f58a35': 'O', '#bcbbbb': 'G'}
for i in range(arr.shape[0]):
for j in range(arr.shape[1]):
# get the text corresponding to the pixel color
txt = colors_to_text.get(to_hex(arr[i,j]/255), '')
# display text (x, y are inverted compared to the i, j indexes of the matrix)
ax.text(j, i, txt, color='#888888', horizontalalignment='center',
verticalalignment='center', fontsize=7)
fig.savefig("foo.pdf", bbox_inches='tight')
The image gave me this result:
Related
I want to make boxplots with hues but I want to color code it so that each specific X string is a certain color with the hue just being a lighter color. I am able to do a boxplot without a hue. When I incorporate the hue, I get the second boxplot which loses the colors. Can someone help me customize the colors for the figure that contains the hue?
Essentially, its what the answer for this question is but with boxplots.
This is my code:
first boxplot
order=['Ash1','E1A','FUS','p53']
colors=['gold','teal','darkorange','royalblue']
color_dict=dict(zip(order,colors))
fig,ax=plt.subplots(figsize=(25,15))
bp=sns.boxplot(data=df_idrs, x=df_idrs["construct"], y=df_idrs['Norm_Ef_IDR/Ef_GS'],ax=ax,palette=color_dict)
sns.stripplot(ax=ax,y='Norm_Ef_IDR/Ef_GS', x='construct', data=df_idrs,palette=color_dict,
jitter=1, marker='o', alpha=0.4,edgecolor='black',linewidth=1, dodge=True)
ax.axhline(y=1,linestyle="--",color='black',linewidth=2)
plt.legend(loc='upper left', bbox_to_anchor=(1.03, 1))
second boxplot
order=['Ash1','E1A','FUS','p53']
colors=['gold','teal','darkorange','royalblue']
color_dict=dict(zip(order,colors))
fig,ax=plt.subplots(figsize=(25,15))
bp=sns.boxplot(data=df_idrs, x=df_idrs["construct"], y=df_idrs['Norm_Ef_IDR/Ef_GS'],ax=ax, hue=df_idrs["location"])
sns.stripplot(y='Norm_Ef_IDR/Ef_GS', x='construct', data=df_idrs, hue=df_idrs["location"],
jitter=1, marker='o', alpha=0.4,edgecolor='black',linewidth=1, dodge=True)
ax.axhline(y=1,linestyle="--",color='black',linewidth=2)
plt.legend(loc='upper left', bbox_to_anchor=(1.03, 1))
The only thing that changed was the palette to hue. I have seen many examples on here but I am unable to get them to work. Using the second code, I have tried the following:
Nothing happens for this one.
for ind, bp in enumerate(ax.findobj(PolyCollection)):
rgb = to_rgb(colors[ind // 2])
if ind % 2 != 0:
rgb = 0.5 + 0.5 * np.array(rgb) # make whiter
bp.set_facecolor(rgb)
I get index out of range for the following one.
for i in range(0,4):
mybox = bp.artists[i]
mybox.set_facecolor(color_dict[order[i]])
Matplotlib stores the boxes in ax.patches, but there are also 2 dummy patches (used to construct the legend) that need to be filtered away. The dots of the stripplot are stored in ax.collections. There are also 2 dummy collections for the legend, but as those come at the end, they don't form a problem.
Some remarks:
sns.boxplot returns the subplot on which it was drawn; as it is called with ax=ax it will return that same ax
Setting jitter=1in the stripplot will smear the dots over a width of 1. 1 is the distance between the x positions, and the boxes are only 0.4 wide. To avoid clutter, the code below uses jitter=0.4.
Here is some example code starting from dummy test data:
from matplotlib import pyplot as plt
from matplotlib.legend_handler import HandlerTuple
from matplotlib.patches import PathPatch
from matplotlib.colors import to_rgb
import seaborn as sns
import pandas as pd
import numpy as np
np.random.seed(20230215)
order = ['Ash1', 'E1A', 'FUS', 'p53']
colors = ['gold', 'teal', 'darkorange', 'royalblue']
hue_order = ['A', 'B']
df_idrs = pd.DataFrame({'construct': np.repeat(order, 200),
'Norm_Ef_IDR/Ef_GS': (np.random.normal(0.03, 1, 800).cumsum() + 10) / 15,
'location': np.tile(np.repeat(hue_order, 100), 4)})
fig, ax = plt.subplots(figsize=(12, 5))
sns.boxplot(data=df_idrs, x=df_idrs['construct'], y=df_idrs['Norm_Ef_IDR/Ef_GS'], hue='location',
order=order, hue_order=hue_order, ax=ax)
box_colors = [f + (1 - f) * np.array(to_rgb(c)) # whiten colors depending on hue
for c in colors for f in np.linspace(0, 0.5, len(hue_order))]
box_patches = [p for p in ax.patches if isinstance(p, PathPatch)]
for patch, color in zip(box_patches, box_colors):
patch.set_facecolor(color)
sns.stripplot(y='Norm_Ef_IDR/Ef_GS', x='construct', data=df_idrs, hue=df_idrs['location'],
jitter=0.4, marker='o', alpha=0.4, edgecolor='black', linewidth=1, dodge=True, ax=ax)
for collection, color in zip(ax.collections, box_colors):
collection.set_facecolor(color)
ax.axhline(y=1, linestyle='--', color='black', linewidth=2)
handles = [tuple(box_patches[i::len(hue_order)]) for i in range(len(hue_order))]
ax.legend(handles=handles, labels=hue_order, title='hue category',
handlelength=4, handler_map={tuple: HandlerTuple(ndivide=None, pad=0)},
loc='upper left', bbox_to_anchor=(1.01, 1))
plt.tight_layout()
plt.show()
I am currently making a plot on matplotlib, which looks like below.
The code for which is:
fig, ax1 = plt.subplots(figsize=(20,5))
ax2 = ax1.twinx()
# plt.subplots_adjust(top=1.4)
ax2.fill_between(dryhydro_df['Time'],dryhydro_df['Flow [m³/s]'],0,facecolor='lightgrey')
ax2.set_ylim([0,10])
AB = ax2.fill_between(dryhydro_df['Time'],[12]*len(dryhydro_df['Time']),9.25,facecolor=colors[0],alpha=0.5,clip_on=False)
ab = ax2.scatter(presence_df['Datetime'][presence_df['AB']==True],[9.5]*sum(presence_df['AB']==True),marker='X',color='black')
# tidal heights
ax1.plot(tide_df['Time'],tide_df['Tide'],color='dimgrey')
I want the blue shaded region and black scatter to be above the plot. I can move the elements above the plot by using clip_on=False but I think I need to extend the space above the plot to do visualise it. Is there a way to do this? Mock-up of what I need is below:
You can use clip_on=False to draw outside the main plot. To position the elements, an xaxis transform helps. That way, x-values can be used in the x direction, while the y-direction uses "axes coordinates". ax.transAxes() uses "axes coordinates" for both directions.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dates = pd.date_range('2018-07-01', '2018-07-31', freq='H')
xs = dates.to_numpy().astype(float)
ys = np.sin(xs * .091) * (np.sin(xs * .023) ** 2 + 1)
fig, ax1 = plt.subplots(figsize=(20, 5))
ax1.plot(dates, ys)
ax1.scatter(np.random.choice(dates, 10), np.repeat(1.05, 10), s=20, marker='*', transform=ax1.get_xaxis_transform(),
clip_on=False)
ax1.plot([0, 1], [1.05, 1.05], color='steelblue', lw=20, alpha=0.2, transform=ax1.transAxes, clip_on=False)
plt.tight_layout() # fit labels etc. nicely
plt.subplots_adjust(top=0.9) # make room for the additional elements
plt.show()
I will start out by showing the plot I'm getting:
What I'm trying to do is to have the value of each position to display at the center of each square, that I managed to do, although, it is not visible because I change the map to be binary (black and white). However when I add the grid lines to have them look separated they intersect on top of the values, as shown in the image above.
The array I'm plotting is only 3x3. Therefore, I would like to see nine equal boxes in total with the values at the center of each respective box. Anyone has an idea on how to do this? Here is the piece of my script that does this.
fig, ax = plt.subplots()
ax.matshow(np.zeros((3,3)), cmap='binary')
ax.grid(which='major', color='black', linestyle='-', linewidth=1)
# To display the values
for (i, j), z in np.ndenumerate(ipcavg):
ax.text(j, i, '{:0.2f}'.format(z), ha='center', va='center')
plt.show()
Thanks!
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.matshow(np.zeros((3,3)), cmap='binary')
ax.grid(which='major', color='black', linestyle='-', linewidth=1)
# To display the values
ax.set_xlim([0,3])
ax.set_ylim([0,3])
for (i, j), z in [[(0,0),0],[(1,1),1],[(2,2),2],[(3,3),3],[(2,3),4],[(1,3),5],[(2,1),6],[(3,2),7],[(1,2),7],[(3,1),8]]:
ax.text(j-0.5, i-0.5, '{:0.2f}'.format(z), ha='center', va='center')
plt.show()
use set_xlim,set_ylim to draw correctly the boxes
substract from x,y half of box height and provide coordinates for upper rightcorner of box
I have the following 3x3 matrix which I would like to plot:
import matplotlib.cm
import matplotlib.pyplot as plt
import numpy as np
import copy
cmap = copy.copy(cm.get_cmap("Blues"))
cmap.set_bad('white')
fig = plt.figure(figsize=(15, 10))
img = np.array([[-0.9, -0.5599234, 0.21042876],[-0.42735877, 0.61514954, -0.74305015],[0.61958201, -0.04358633, 0.78672511]])
im = plt.imshow(img, origin='upper', cmap=cmap)
The result looks as follows:
As visible the top left entry is smallest and should be displayed as white. How can I change it in such a way so that the smallest entry is displayed in white?
Second, is there a way to adapt the colormap such that it starts with darker values?
One way to have a colormap start with white, is to create a ListedColormap, e.g. going from white to darkblue. To start with the darkest color, just reverse the list of colors for the ListedColormap.
A standard colormap can be reversed, just by appending _r at the end of its name.
One way to create a colormap going from a mid-range to a dark blue, is creating a ListedColormap where the rgb-values are given as hexadecimal.
Here are some examples:
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import numpy as np
img = np.array([[-0.9, -0.5599234, 0.21042876], [-0.42735877, 0.61514954, -0.74305015], [0.61958201, -0.04358633, 0.78672511]])
fig, axs = plt.subplots(ncols=3, figsize=(12, 5))
cmap0 = LinearSegmentedColormap.from_list('', ['white', 'darkblue'])
cmap1 = 'Blues_r'
cmap2 = LinearSegmentedColormap.from_list('', ['#aaddee', '#000077'])
for ax, cmap in zip(axs, [cmap0, cmap1, cmap2]):
im = ax.imshow(img, origin='upper', cmap=cmap)
plt.colorbar(im, ax=ax, orientation='horizontal', pad=0.05)
ax.set_xticks([0, 1, 2])
ax.set_yticks([0, 1, 2])
ax.tick_params(labelbottom=False, labelleft=False, length=0) # hide ticks, but use position for a grid
ax.grid(True, color='white')
axs[0].set_title("Colormap from white to darkblue")
axs[1].set_title("Reversed blues colormap")
axs[2].set_title("Custom darker blues colormap")
plt.show()
Also of interest might be Seaborn's palette functions, which provide additional ways to create colormaps (the parameter as_cmap=True is needed for these functions to return a colormap).
I am trying to explore a subplot 2 plots with square in shape rotated by 45 degree.
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
fig, ax= plt.subplots(1,2)
ax[0].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[0].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[0].set_xticks(np.arange(-.5, 10, 1));
ax[0].set_yticks(np.arange(-.5, 10, 1));
ax[1].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[1].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[1].set_xticks(np.arange(-.5, 10, 1));
ax[1].set_yticks(np.arange(-.5, 10, 1));
plt.show()
Actual Result is :-
I want to rotate individual plot by 45 degree. Something like:-
I am trying to find in Matplotlib Documentation. Still not getting. Any help?
Please note this is NOT DUPLICATE OF
Is there a way to rotate a matplotlib plot by 45 degrees?
The mentioned URL is for a plot. and the solution is to rotate IMAGE. However this is pertaining to Subplot. I want to rotate PLOT not image as whole.
Based on this link and documentation about floating_axes, you can try something like this:
from mpl_toolkits.axisartist.grid_finder import DictFormatter
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib import colors
import numpy as np
def setup_axes1(fig, rect, angle):
tr = Affine2D().scale(2, 2).rotate_deg(angle)
#We create dictionarys to keep the xticks and yticks after the rotation
dictio={i:str(val) for i,val in enumerate(np.arange(-.5, 10, 1).tolist())}
reversedictio={i:dictio[val] for i,val in enumerate(list(reversed(sorted(dictio.keys()))))}
grid_helper = floating_axes.GridHelperCurveLinear(
tr, extremes=(-0.5, 9.5,-0.5, 9.5), tick_formatter1= DictFormatter(dictio),
tick_formatter2=DictFormatter(reversedictio))
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
aux_ax = ax1.get_aux_axes(tr)
grid_helper.grid_finder.grid_locator1._nbins = 10 #Number of rows
grid_helper.grid_finder.grid_locator2._nbins = 10 #Number of columns
return aux_ax
fig1, axes=plt.subplots(2,figsize=(20,20))
plt.rcParams.update({'font.size': 27})
#We erase the first previous axes
fig1.delaxes(axes[0])
fig1.delaxes(axes[1])
data = np.random.rand(10, 10) * 20
#We create the floating_axes
ax0 = setup_axes1(fig1, 121,-45)
ax1 = setup_axes1(fig1, 122,-45)
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
ax0.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax0.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax1.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax1.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.show()
Output:
Or, as an other alternative, I found a "tricky" way to do it, and it's about catching the figures in the buffer, rotate them -45 degrees, and then merge them into a single image, and since you have the same two images, you can try something like this:
import matplotlib
import io
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
##PLOTING THE FIGURE##
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
#We change style values to get the image with better quality
plt.rcParams.update({'font.size': 46})
plt.figure(figsize=(20,20))
plt.imshow(data, cmap=cmap, norm=norm)
# draw gridlines
plt.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.gca().set_xticks(np.arange(-.5, 10, 1));
plt.gca().set_yticks(np.arange(-.5, 10, 1));
##SAVING THE FIGURE INTO AN IMAGE##
#We save the current figure as a Image
buf = io.BytesIO()
plt.savefig(buf, format='png',bbox_inches='tight')
buf.seek(0)
im = Image.open(buf) #We open the current image saved in the buffer
#We rotate the image and fill the background with white
img_01=im.rotate(-45, Image.NEAREST, expand = 1, fillcolor = (255,255,255))
buf.close()
##MERGING THE TWO FIGURES##
new_im = Image.new('RGB', (2*img_01.size[0]+20,img_01.size[1]), 'white')
mouse_mask = img_01.convert('RGBA')
new_im.paste(img_01, (0,0))
new_im.paste(img_01, (img_01.size[0]+8,0))
new_im.save("merged_images.png", 'PNG') #Important(just to clarify): save the image, since the buffer is renewed every time you run the script
new_im.show()
Output:
I helped myself with these links:
How-to-merge-images-with-same-size-using-the-python-3-module-pillow
how-to-save-a-pylab-figure-into-in-memory-file-which-can-be-read-into-pil-image
python-pillow-rotate-image-90-180-270-degrees
specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand