How to encircle some pixels on a heat map with a continuous, not branched line using Python?

I tried something that should fit your needs.

First of all, I defined an arbitrary data:

X,Y = np.meshgrid(range(30),range(30))

Z = np.sin(X)+np.sin(Y)

Here you can define the condition which fits in the pattern you want to highlight:

selected = Z>1.5

To plot you will use scatter instead of imshow. You will plot all the data, then the selected data two more times, one with larger squares with the highlight color and another normally using the same color reference and limits.

info = dict(marker='s',vmin=-2,vmax=2)
fig,ax = plt.subplots()
plt.scatter(X.ravel(),Y.ravel(),100,c=Z.ravel(),**info)
plt.scatter(X[selected].ravel(),Y[selected].ravel(),150,c='r',marker='s')
plt.scatter(X[selected].ravel(),Y[selected].ravel(),100,c=Z[selected].ravel(),**info)
ax.axis('equal')

enter image description here


Similar to the answer in Can matplotlib contours match pixel edges? you can create a grid with a higher resolution and draw a contour plot.

import numpy as np
import matplotlib.pyplot as plt

X,Y = np.meshgrid(range(30),range(30))
Z = np.sin(X)+np.sin(Y)

resolution = 25

f = lambda x,y: Z[int(y),int(x) ]
g = np.vectorize(f)

x = np.linspace(0,Z.shape[1], Z.shape[1]*resolution)
y = np.linspace(0,Z.shape[0], Z.shape[0]*resolution)
X2, Y2= np.meshgrid(x[:-1],y[:-1])
Z2 = g(X2,Y2)


plt.pcolormesh(X,Y, Z)
plt.contour(X2,Y2,Z2, [1.5], colors='r', linewidths=[1])

plt.show()

enter image description here


Another solution which works for me:

Lets have a grid for example:

grid=[[0, 6, 8, 2, 2, 5, 25, 24, 11],
      [4, 15, 3, 22, 225, 1326, 2814, 1115, 18],
      [6, 10, 9, 201, 3226, 3549, 3550, 3456, 181],
      [42, 24, 46, 1104, 3551, 3551, 3551, 3382, 27],
      [9, 7, 73, 2183, 3551, 3551, 3551, 3294, 83],
      [9, 7, 5, 669, 3544, 3551, 3074, 1962, 18],
      [10, 3545, 9, 10, 514, 625, 16, 14, 5],
      [5, 6, 128, 10, 8, 6, 7, 40, 4]]

We plot it:

plt.pcolormesh(grid)

enter image description here

Assume we want to encircle every pixel with value higher than 1420. We create a boolean array:

threshold=1420
booleangrid=np.asarray(grid)>threshold
intgrid=booleangrid*1

We then create a line segment around every pixel:

down=[];up=[];left=[];right=[]
for i, eachline in enumerate(intgrid):
    for j, each in enumerate(eachline):
        if each==1:
            down.append([[j,j+1],[i,i]])
            up.append([[j,j+1],[i+1,i+1]])
            left.append([[j,j],[i,i+1]])
            right.append([[j+1,j+1],[i,i+1]])

and join them together:

together=[]
for each in down: together.append(each)
for each in up: together.append(each)
for each in left: together.append(each)
for each in right: together.append(each)

(Creted separately for clarity.)

We go through these individual line segments, ant keep only those which appear only once, ie the ones on the edge of the feature defined by the boolean array (booleangrid) we defined above:

filtered=[]
for each in together:
    c=0
    for EACH in together:
        if each==EACH:
            c+=1
    if c==1:
        filtered.append(each)

Then we plot the grid and idividual line segments with a for loop:

plt.pcolormesh(grid)
for x in range(len(filtered)):
    plt.plot(filtered[x][0],filtered[x][1],c='red', linewidth=8)

giving us the result:

enter image description here

With which we can be happy with.