Drape outline of active values over another 2D array - python

Lets say I have a simple 2D numpy array that I display with imshow():
import numpy as np
import random
import matplotlib.pyplot as plt
a = np.random.randint(2, size=(10,10))
im = plt.imshow(a, cmap='spring', interpolation='none', vmin=0, vmax=1, aspect='equal')
plt.show()
And I have another 2D numpy array, like so:
bnd = np.zeros((10,10))
bnd[2,3] = bnd[3,2:5] = bnd[4,3] = 1
bnd[6,6] = bnd[7,5:8] = bnd[8,6] = 1
plt.imshow(bnd)
plt.show()
How can I generate an outline of all the continuous values of "1" in bnd and then overplot it on a, so I get something like the following (I manually added the black lines in the example below)?

You can compute the borders of the mask by finding the starting and ending indices of consecutive ones and converting those to border segments with coordinates of the image.
Setting up the image and the mask
import numpy as np
import matplotlib.pyplot as plt
a = np.random.randint(2, size=(10,10))
plt.imshow(a, cmap='spring', interpolation='none', vmin=0, vmax=1, aspect='equal')
bnd = np.zeros((10,10))
kernel = [[0,1,0],
[1,1,1],
[0,1,0]]
bnd[2:5, 2:5] = bnd[6:9, 5:8] = kernel
Finding the indices and convert them to coordinates of the image
# indices to vertical segments
v = np.array(np.nonzero(np.diff(bnd, axis=1))).T
vs = np.repeat(v, 3, axis=0) - np.tile([[1, 0],[0, 0],[np.nan, np.nan]], (len(v),1))
# indices to horizontal segments
h = np.array(np.nonzero(np.diff(bnd, axis=0))).T
hs = np.repeat(h, 3, axis=0) - np.tile([[0, 1],[0, 0],[np.nan, np.nan]], (len(h),1))
# convert to image coordinates
bounds = np.vstack([vs,hs])
x = np.interp(bounds[:,1], plt.xlim(), (0, bnd.shape[1]))
y = np.interp(bounds[:,0], sorted(plt.ylim()), (0, bnd.shape[0]))
plt.plot(x, y, color=(.1, .1, .1, .6), linewidth=5)
plt.show()
Output

Related

How to overlay a pcolormesh with binary information in Python

Given a 2D array
172,47,117
192,67,251
195,103,9
211,21,242
The objective is to place markers (e.g., shape, line) overlapping to the 2D images with reference to a binary 2D coordinate below
0,1,0
0,0,0
0,1,0
1,1,0
Specifically, a marker will be place if the cell is equivalent to 1.
The expected output is as below. In this example, the marker is in the form of horizontal red line. However, the marker can be of any other shape that make the code more straight forward.
May I know how to achieve this with any graphical packages in Python?
The above coordinate can be reproduced by following
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
X=np.random.randint(256, size=(4, 3))
arrshape=np.random.randint(2, size=(4, 3))
fig = plt.figure(figsize=(8,6))
plt.pcolormesh(X,cmap="plasma")
plt.title("Plot 2D array")
plt.colorbar()
plt.show()
You can throw in a scatter:
x,y = X.shape
xs,ys = np.ogrid[:x,:y]
# the non-zero coordinates
u = np.argwhere(arrshape)
plt.scatter(ys[:,u[:,1]].ravel()+.5,
xs[u[:,0]].ravel()+0.5,
marker='x', color='r', s=100)
Output:
One way to do it is with matplotlib
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
X=np.random.randint(256, size=(4, 3))
arrshape=np.random.randint(2, size=(4, 3))
fig = plt.figure(figsize=(8,6))
plt.pcolormesh(X,cmap="plasma")
plt.title("Plot 2D array")
plt.colorbar()
markers = [
[0,1,0],
[0,0,0],
[0,1,0],
[1,1,0]]
draw = []
for y, row in enumerate(markers):
prev = 0
for x, v in enumerate(row):
if v == 1:
plt.plot([x+0.25, x+0.75], [y+0.5, y+0.5], 'r-', linewidth=5)
if prev == 1:
plt.plot([x-0.5, x+0.5], [y+0.5, y+0.5], 'r-', linewidth=5)
prev = v
plt.show()
Output:
(Since your markers are upside-down)

Is there a cmap which goes from transparant to black (or any other color)?

I'm using the following cmap="binary" on a datasets with zeros and ones. The cmap goes from white to black. This results in the following figure:
Black and white figure:
Because I want to overlap this graph with an existing graph, I want to keep the black but make the white transparant.
Overlapping image with white:
Is there a cmap which goes from transparancy to black ?
I'm using the following code to plot the graphs:
plt.pcolor(mp1,cmap="binary",alpha=0.5)
Found the solution!
c_white = matplotlib.colors.colorConverter.to_rgba('white',alpha = 0)
c_black= matplotlib.colors.colorConverter.to_rgba('black',alpha = 1)
cmap_rb = matplotlib.colors.LinearSegmentedColormap.from_list('rb_cmap',[c_white,c_black],512)
pl = plt.pcolor(mp1,cmap=cmap_rb)
It's not too difficult to define your own RGBA colour-maps (source). For example, to define a black colour map with linearly varying transparency:
from matplotlib.colors import ListedColormap
cmap = np.zeros([256, 4])
cmap[:, 3] = np.linspace(0, 1, 256)
cmap = ListedColormap(cmap)
Example usage:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
x = 2 * np.pi * np.linspace(-1, 1, 100)
z = np.sin(x.reshape(1, -1) + x.reshape(-1, 1))
cmap = np.zeros([256, 4])
cmap[:, 3] = np.linspace(0, 1, 256)
cmap = ListedColormap(cmap)
plt.figure()
plt.pcolormesh(z + 1, cmap='bwr', edgecolors=None)
plt.pcolormesh(np.fliplr(z), cmap=cmap, edgecolors=None)
plt.savefig("temp")
Output:
If mp1 only has the values 0 and 1, and nothing inbetween, you can mask out the zeros and only plot the ones. np.where(mp1 == 1, 1, np.nan) will only show the ones. Using vmin=0 will make sure 0 maps to the white of the 'binary' colormap and vmax=1 will map 1 to black.
import matplotlib.pyplot as plt
import numpy as np
mp0 = np.random.randn(15, 30).cumsum(axis=1).cumsum(axis=0) # random backgroud
mp1 = np.zeros((15, 30)) # start with all zeros
mp1[np.random.randint(0, 15, 30), np.random.randint(0, 30, 30)] = 1 # set some random positions to 1
plt.pcolormesh(mp0, cmap='rainbow')
plt.pcolormesh(np.where(mp1 == 1, 1, np.nan), cmap='binary', vmin=0, vmax=1)
plt.tight_layout()
plt.show()

Overlaying plots on a single graph

I have two heatmaps which are based on 2d histograms that I am trying to overlay on a single graph. The limits of their axes (extent_L and extent_H) do not necessarily coincide exactly. I can make the individual plots satisfactorily if needed, but when trying to show both heatmaps on a single graph nicely, only the most recent one is displayed.
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = [xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]]
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = [xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]]
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T, extent=extent_H, origin='lower', cmap='Greens')
plt.show()
Edit: If I'm not mistaken, all points are not in exactly the proper location
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = np.array([xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]])
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = np.array([xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]])
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T, extent=extent_H, origin='lower', cmap='Greens')
plt.autoscale()
plt.show()
flatHMH = np.reshape(heatmap_H, 2500) # flatten the 2D arrays
flatHML = np.reshape(heatmap_L, 2500)
maxHMH = flatHMH.max() # Find the maximum in each
maxHML = flatHML.max()
# Now for each value in the flat array build an RGBA tuple using
# 1 for the colour we want - either green or blue, and then scaling
# the value by the maximum, finally reshaping back to a 50x50 array
augHMH = np.array([(0, 1, 0, x/maxHMH) for x in flatHMH]).reshape((50, 50, 4))
augHML = np.array([(0, 0, 1, x/maxHML) for x in flatHML]).reshape((50, 50, 4))
plt.clf()
# Plot without cmap as colours are now part of the data array passed.
im1 = plt.imshow(augHML, extent=extent_L, origin='lower')
im2 = plt.imshow(augHMH, extent=extent_H, origin='lower')
plt.autoscale()
plt.show()
If you look closely at the points in the last plot, for example the clustering of points at the edge, you'll notice they are not the same as in the plot above.
You are displaying both plots, the problem is that you are drawing one on top of the other. To see this in action you can shift one of the plots as in:
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = np.array([xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]])
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = np.array([xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]])
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T+2, extent=extent_H+2, origin='lower', cmap='Greens')
plt.autoscale()
plt.show()
You also need the plt.autoscale() call in there as otherwise the limits are not adjusted correctly.
One way to show the two plots on top of each other is to use the argument alpha=X to the imshow call (where 0 < X < 1) in order to set transparency on the plot call. Another, possibly clearer way is to transform each value from the histogram2D to an RGBA value. See the imshow docs for both alternatives to displaying the plots on top of each other.
One way of transforming the values would be to flatten the data, and augment it with the colours you want.
# imports and test data generation as before, removed for clarity...
flatHMH = np.reshape(heatmap_H, 2500) # flatten the 2D arrays
flatHML = np.reshape(heatmap_L, 2500)
maxHMH = flatHMH.max() # Find the maximum in each
maxHML = flatHML.max()
# Now for each value in the flat array build an RGBA tuple using
# 1 for the colour we want - either green or blue, and then scaling
# the value by the maximum, finally reshaping back to a 50x50 array
augHMH = np.array([(0, 1, 0, x/maxHMH) for x in flatHMH]).reshape((50, 50, 4))
augHML = np.array([(0, 0, 1, x/maxHML) for x in flatHML]).reshape((50, 50, 4))
plt.clf()
# Plot without cmap as colours are now part of the data array passed.
im1 = plt.imshow(augHML, extent=extent_L, origin='lower')
im2 = plt.imshow(augHMH, extent=extent_H, origin='lower')
plt.autoscale()
plt.show()
You can call
plt.autoscale()
such that the limits are adjusted to the content of the axes.
Example:
import numpy as np
import matplotlib.pyplot as plt
def get(offs=0):
# Generate some test data
x = np.random.randn(8873)+offs
y = np.random.randn(8873)+offs
heatmap, xedges, yedges = np.histogram2d(x, y, bins=50)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
heatmap, xedges, yedges = np.histogram2d(x, y, bins=50)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
return heatmap, extent
h1,e1 = get(-3)
h2,e2 = get(+3)
plt.imshow(h1, extent=e1, origin='lower', cmap="RdBu")
plt.imshow(h2, extent=e2, origin='lower', cmap="YlGnBu")
plt.autoscale()
plt.show()

Plot aligned x,y 1d histograms from projected 2d histogram

I need to generate an image similar to the one shown in this example:
The difference is that, instead of having the scattered points in two dimensions, I have a two-dimensional histogram generated with numpy's histogram2d and plotted using with imshow and gridspec:
How can I project this 2D histogram into a horizontal and a vertical histogram (or curves) so that it looks aligned, like the first image?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
data = # Uploaded to http://pastebin.com/tjLqM9gQ
# Create a meshgrid of coordinates (0,1,...,N) times (0,1,...,N)
y, x = np.mgrid[:len(data[0, :, 0]), :len(data[0, 0, :])]
# duplicating the grids
xcoord, ycoord = np.array([x] * len(data)), np.array([y] * len(data))
# compute histogram with coordinates as x,y
h, xe, ye = np.histogram2d(
xcoord.ravel(), ycoord.ravel(),
bins=[len(data[0, 0, :]), len(data[0, :, 0])],
weights=stars.ravel())
# Projected histograms inx and y
hx, hy = h.sum(axis=0), h.sum(axis=1)
# Define size of figure
fig = plt.figure(figsize=(20, 15))
gs = gridspec.GridSpec(10, 12)
# Define the positions of the subplots.
ax0 = plt.subplot(gs[6:10, 5:9])
axx = plt.subplot(gs[5:6, 5:9])
axy = plt.subplot(gs[6:10, 9:10])
ax0.imshow(h, cmap=plt.cm.viridis, interpolation='nearest',
origin='lower', vmin=0.)
# Remove tick labels
nullfmt = NullFormatter()
axx.xaxis.set_major_formatter(nullfmt)
axx.yaxis.set_major_formatter(nullfmt)
axy.xaxis.set_major_formatter(nullfmt)
axy.yaxis.set_major_formatter(nullfmt)
# Top plot
axx.plot(hx)
axx.set_xlim(ax0.get_xlim())
# Right plot
axy.plot(hy, range(len(hy)))
axy.set_ylim(ax0.get_ylim())
fig.tight_layout()
plt.savefig('del.png')
If you are ok with the marginal distributions all being upright, you could use corner
E.g.:
import corner
import numpy as np
import pandas as pd
N = 1000
CORNER_KWARGS = dict(
smooth=0.9,
label_kwargs=dict(fontsize=30),
title_kwargs=dict(fontsize=16),
truth_color="tab:orange",
quantiles=[0.16, 0.84],
levels=(1 - np.exp(-0.5), 1 - np.exp(-2), 1 - np.exp(-9 / 2.0)),
plot_density=False,
plot_datapoints=False,
fill_contours=True,
max_n_ticks=3,
verbose=False,
use_math_text=True,
)
def generate_data():
return pd.DataFrame(dict(
x=np.random.normal(0, 1, N),
y=np.random.normal(0, 1, N)
))
def main():
data = generate_data()
fig = corner.corner(data, **CORNER_KWARGS)
fig.show()
if __name__ == "__main__":
main()

imshow with non-orthogonal axes (i.e. parallelogram )

I have 2D array of data sampled along two vectors non-orthogonal a, b
a = |a|.( cos(alfa), sin(alfa) )
b = |b|.( cos(beta), sin(beta) )
(i.e not along orthogonal cartesian direction x, y)
I would like to plot this data un-distorted (i.e. as parallelogram instead of rectangle)
is there any function to do that in matplotlib?
I need it for plotting data like this (c, f , i)
What about using an affine transform as in this example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
def get_image():
from scipy import misc
Z = misc.imread('31271907.jpg')
return Z
# Get image
fig, ax = plt.subplots(1,1)
Z = get_image()
# image skew
im = ax.imshow(Z, interpolation='none', origin='lower',
extent=[-2, 4, -3, 2], clip_on=True)
im._image_skew_coordinate = (3, -2)
plt.show()
Which uses the image
and turns it into,

Categories

Resources