Want to Apply Blur to a Specific Filled Object on Python Plot - python

I am using Python 3.8.10 on Linux Mint 20.3 Una. I am making a series of animations with a multitude (potentially thousands) of fish shapes, each of which are produced by specifying a 2D profile with points, and is then filled in using the Pyplot fill function.
What I would like to be able to do is to apply a unique blur to each of these individual filled regions based on a computed distance to mimic image depth. An added complication is that these filled regions frequently overlap.
In theory, this could be done by exporting SVG files and manually applying the blurs in Inkscape or some other package, but there are potentially thousands of fish and hundreds of frames, so a way to achieve this in code really is the only realistic way to accomplish it, if it is possible.
Here is the minimal code that produces two filled profiles that I would like to blur individually:
import matplotlib.pyplot as plt
from scipy.ndimage import gaussian_filter
#define profile of object with points
x_profile = [0.5,0.485951301332915,0.423371700761206,0.358237605529776,0.281609306290982,0.23180095266422,0.152618567550257,0.053001860296735,-0.005746611462221,-0.060663545623872,-0.05683323438022,-0.257343937095579,-0.317369329156755,-0.345466399463283,-0.469348762061393,-0.492337251833031,-0.5,-0.439974607938825,-0.418263242861681,-0.415709156986512,-0.461686095651334,-0.492337415346851,-0.483397419850022,-0.466794594429313,-0.363346513092306,-0.342912313588113,-0.31864669912198,-0.289272544999412,-0.236909860226751,-0.210090037250083,-0.183269887245775,-0.146233189348514,-0.078544599457363,0.086206203027589,0.210088361233424,0.310982111424531,0.418261893872663,0.478287408569203,0.493612741389321]
y_profile = [-0.019156461632871,0.002554903444271,0.031928934931474,0.051085805348896,0.065134504015981,0.07024308455087,0.071518492350251,0.067688181106599,0.158365179012477,0.068965632828735,0.049808353626761,0.028096988549618,0.025542085105346,0.03192770857782,0.10217038434414,0.104725287788412,0.091954040843463,0.00255449465972,-0.00255449465972,-0.017879827479838,-0.067688181106599,-0.148148017942698,-0.158365179012477,-0.151979555540003,-0.061302557634125,-0.047254267751592,-0.040868235494567,-0.042143643293948,-0.080457792913345,-0.084288104156997,-0.079179523622108,-0.097059759886497,-0.111108049769031,-0.127710834311284,-0.126435426511903,-0.107278556094481,-0.076627072885143,-0.045975589675805,-0.031927299793271]
#this just makes a second object and offsets it down 0.5 units
n_objects = 2
n_points = len(y_profile)
x_points = np.zeros((n_objects, n_points))
y_points = np.zeros((n_objects, n_points))
for i in range(n_objects):
for j in range(n_points):
x_points[i,j] = x_profile[j]
y_points[i,j] = y_profile[j] - i*0.5
#make plot
fig = plt.figure(frameon=False)
fig.set_size_inches(6.5, 6.5)
ax = plt.axes()
ax.set_facecolor((0,0,1.0))
ax.set_xlim(-1,+1)
ax.set_ylim(-1,+1)
ax.set_aspect('equal', adjustable='box')
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
#create filled regions defined by copies of profile points (I want to be able to apply a blur these fills individually)
for i in range(n_objects):
plt.fill(x_points[i,:], y_points[i,:], color = (0, 0, 0.5))
#tried the following, but does not work at all.
#handle = plt.fill(x_profile, y_profile, color = (0, 0, 0.5))
#blurred = gaussian_filter(handle, sigma=1)
#show plot (normally exporting PNG frames for animation)
plt.show()
which should yield this image:
Fish Profiles
If this is not possible in Python, I'm open to suggestions as to how this could be implemented dynamically in some other way.
I've seen examples of SciPy Gaussian blur applied to regions of static images, but the blur that I want to achieve is specific to the filled "object" which isn't a neat rectangle. I note that when this image is exported as an SVG the individual filled objects appear as distinct entities in that file, but I don't see a way to assign a handle to it within Python and to apply a blur to it. I've tried variations of 'handle = plt.fill(x,y)' and 'gaussian_filter(handle, sigma=1)' but with no success.

I think i was able to do what you are asking for using convolution but it is not optimized for speed at all. plus, it is kind of hard to tell how well it will translate to your bigger code.
Going off of whay you posted, I converted the graphs to rgb arrays and convolved each dimension separately with a from scratch convolution function (not my own,1). this code will output the first fish image and then a few seconds later it will output the blurred fish image.
import matplotlib.pyplot as plt
import numpy as np
import cv2
import plotly.express as px
def Convolve(img, kernel):
(imgX, imgY) = img.shape[:2]
(kernelX, kernelY) = kernel.shape[:2]
#print(imgX,imgY, kernelX,kernelY)
pad = (kernelX - 1) // 2
img = cv2.copyMakeBorder(img, pad, pad, pad, pad, cv2.BORDER_REPLICATE) #top, bottom, left, right
#the above line prevents error with convolution: operands could not be broadcast together with shapes (23,22) (23,23)
output = np.zeros((imgX, imgY), dtype="float32")
#shift kernel vertical and horizontal across image, pad prevents kernel from going out of bounds
for y in np.arange(pad, imgY + pad):
for x in np.arange(pad, imgX + pad):
#locate specific pixel
roi = img[y - pad:y + pad + 1, x - pad:x + pad + 1]
#print(roi)
#perform convolution
k = (roi * kernel).sum()
#populate the result into the previously created np.zeroes array
output[y - pad, x - pad] = k
return output
# define profile of object with points
x_profile = [0.5, 0.485951301332915, 0.423371700761206, 0.358237605529776, 0.281609306290982, 0.23180095266422,
0.152618567550257, 0.053001860296735, -0.005746611462221, -0.060663545623872, -0.05683323438022,
-0.257343937095579, -0.317369329156755, -0.345466399463283, -0.469348762061393, -0.492337251833031, -0.5,
-0.439974607938825, -0.418263242861681, -0.415709156986512, -0.461686095651334, -0.492337415346851,
-0.483397419850022, -0.466794594429313, -0.363346513092306, -0.342912313588113, -0.31864669912198,
-0.289272544999412, -0.236909860226751, -0.210090037250083, -0.183269887245775, -0.146233189348514,
-0.078544599457363, 0.086206203027589, 0.210088361233424, 0.310982111424531, 0.418261893872663,
0.478287408569203, 0.493612741389321]
y_profile = [-0.019156461632871, 0.002554903444271, 0.031928934931474, 0.051085805348896, 0.065134504015981,
0.07024308455087, 0.071518492350251, 0.067688181106599, 0.158365179012477, 0.068965632828735,
0.049808353626761, 0.028096988549618, 0.025542085105346, 0.03192770857782, 0.10217038434414,
0.104725287788412, 0.091954040843463, 0.00255449465972, -0.00255449465972, -0.017879827479838,
-0.067688181106599, -0.148148017942698, -0.158365179012477, -0.151979555540003, -0.061302557634125,
-0.047254267751592, -0.040868235494567, -0.042143643293948, -0.080457792913345, -0.084288104156997,
-0.079179523622108, -0.097059759886497, -0.111108049769031, -0.127710834311284, -0.126435426511903,
-0.107278556094481, -0.076627072885143, -0.045975589675805, -0.031927299793271]
# make plot
fig = plt.figure(frameon=False)
fig.set_size_inches(6.5, 6.5)
ax = plt.axes()
ax.set_facecolor((0, 0, 1.0))
ax.set_xlim(-1, +1)
ax.set_ylim(-1, +1)
ax.set_aspect('equal', adjustable='box')
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
# create filled region defined by profile points (I want to be able to apply a blur to this)
plt.fill(x_profile, y_profile, color=(0, 0, 0.5))
fig.canvas.draw()
data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
array=np.array(data)
R=array[:,:,0]
G=array[:,:,1]
B=array[:,:,2]
fig=px.imshow(array, color_continuous_scale="gray")
fig.show()
_1DKern = cv2.getGaussianKernel(33, 2) # first value is dimensions, second is sigma
_2DKern = np.outer(_1DKern, _1DKern.transpose())
convR = Convolve(R, _2DKern)
convG=Convolve(G,_2DKern)
convB=Convolve(B,_2DKern)
conv=np.stack([convR,convG,convB],2)
fig = px.imshow(conv, color_continuous_scale="gray")
fig.show()

Related

How to morph two grid-like images seamlessly?

I have two images that consist of colored squares with different grid step (10x10 and 12x12).
What I want is to make the first image to be smoothly transformed into the second one.
When I use a plain image overlay with cv2.addWeighted() function, the result (left) is not good because of the intersected grid spaces. I suppose it would be better to shift remaining grid cells to the borders and clear out the rest (right).
Is there any algorithm to deal with this task?
Thanks.
You can interpolate each pixel individually between different images.
import numpy as np
from scipy import interpolate
import matplotlib.pyplot as plt
np.random.seed(200)
num_images = 2
images = np.random.rand(num_images, 8,8)
for index, im in enumerate(images):
print(f'Images {index}')
fig = plt.imshow(im)
plt.show()
Interpolating these images:
n_frames = 4
x_array = np.linspace(0, 1, int(n_frames))
def interpolate_images(frame):
intermediate_image = np.zeros((1, *images.shape[1:]))
for lay in range(images.shape[1]):
for lat in range(images.shape[2]):
tck = interpolate.splrep(np.linspace(0, 1, images.shape[0]), images[:, lay, lat], k = 1)
intermediate_image[:, lay, lat] = interpolate.splev(x_array[frame], tck)
return intermediate_image
for frame in range(n_frames):
im = interpolate_images(int(frame))
fig = plt.imshow(im[0])
plt.show()

How to display pixel variance over time from a set of images? (brightness changing object imaged over time)

I would like to display with a heat map the change in intensity/brightness over time of a set of images. These are images of a brightness-changing object imaged over time. This would be useful to see which parts of the object (which pixels) have the highest variance in brightness.
I'm currently using OpenCV to manipulate these images, but cannot find any straightforward way of getting this heatmap. In addition to this, if anyone could suggest a way of calculating the variance without having to create a separate array for the values for each pixel (maybe calculating it directly from the stack of images?) it would be helpful too.
This in an example of what one of the images looks like
Generate some synthetic data:
All pixes change with std of 3
Some pixes change (in shape X) with std of 5
Code:
import cv2
lena = cv2.imread("lena.png", 0)
lena = cv2.resize(dices, (100,100))
images = np.zeros((30, *lena.shape))
images[0] = lena.astype('float64')
mask = np.rot90(np.eye(100)) + np.eye(100)
for i in range(1,30):
img = images[i-1]
img += np.random.randn(*lena.shape)*3
img += mask*5
images[i] = img
The set of images created look like below
code to render images:
plt.close('all')
plt.figure(figsize=(25,25))
for i in range(25):
plt.subplot(5,5,i+1)
plt.imshow(images[i],cmap='gray')
plt.xticks([])
plt.yticks([])
plt.tight_layout()
plt.show()
Finally, heatmap to find the portions of the image which change at a different speed.
import seaborn as sns; sns.set()
ax = sns.heatmap(images.std(axis=0))
plt.show()
We got our mask back.

for loop to remove values not in an array in python

I have a set of images in a numpy array. After some processing and applying a threshold I turned them into images that have either value 0 or 1 in each xy coordinate. I want to use a for loop and nonzero to turn the xy coordinates of the original image that are not in the nonzero array to zero and leave the pixels in the nonzero array with their original intensity. Im a complete noob in programming and I have been given this task.
This is what I have so far but the last part doesn't work:
import cv2
# Taking the first image of the data
image = series_copy2[0,:,:]
# Mean total background of the image
print('Mean total background = ' +str(np.mean(image)) + ' counts.')
# Threshold for background removal
threshold =30
# Setting all pixels below a threshold to zero to remove the background
image[image[:,:] < threshold] = 0
image[image[:,:]>threshold]=1
# Plotting the result for checking
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
data = image
plt.tight_layout()
im = plt.imshow(data, interpolation = 'nearest')
np.transpose(np.nonzero(data))
nz_arrays=np.transpose(np.nonzero(data))
#this doesn't work
for x in data:
if image[image[:,:] not in nz_arrays]=0
# Plotting the result for checking
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
data = image
plt.tight_layout()
im = plt.imshow(data, interpolation = 'nearest')
#this doesn't work
for x in data:
if image[image[:,:] not in nz_arrays] is 0:
# What is this doing?
When using if, you need to end it with a colon and then write the function.

matplotlib markers / mask on image pixels

So I have an image and I have a pixel mask for that image, where the mask is the same size as the image and contains values of 0 and 1, where if it is 0 I don't want to modify the image, and if it is 1 I want to add a transparent color over that pixel of the image.
Basically I want to highlight certain segments of the image but still see what is underneath.
Now I have searched high and low but haven't found a simple way to do this. I used np.where with the mask to get the pixel locations of the 1's to use with the plot functions. I first tried scatter plots with a small marker size and no edge color (small scatter plot markers in matplotlib are always black), but the markers are not one image pixel in size, they seem to be an absolute size and so depending on the size of the figure the transparency is affected and weird patterns are created from the overlapping markers.
Just the regular pyplot plot function created the exact look I desired (where the coloring was smooth and invariant to figure size) but it also colored horizontal connections between disjoint segments in the mask (since it is drawing lines I guess), so I couldn't use that.
What worked the best was patches, which I came across in this question: (How to set a fixed/static size of circle marker on a scatter plot?). I found that rectangular patches with width and height of 1 gave me the exact desired effect, where I could put a transparent color over certain pixels of the image. However this proved to produce a ton (tens of thousands) of rectangles for certain images, and so it was quite slow. Even when using a PatchCollection instead of calling addPatch every time it was still slow.
Now I can probably just join adjacent rectangles to reduce the number of things needing to be drawn, but I was just wondering if there was an easier way to do this?
Thanks.
You can do a semitransparent overlay either using masked arrays or by setting the alpha values in an RGBA image. Here are both worked through (using the example of three semitransparent red squares placed over a circular pattern), and they give similar images (so I'll only show one):
from pylab import *
from numpy import ma
x = y = linspace(-6, 6, 100)
X, Y = meshgrid(x, y)
z3 = X*X + Y*Y # circular pattern
# first, do this with a masked array
figure()
# z4 = 3 diagonal square
# zm = a uniform image (ones), with a mask of squares (~z4)
z4 = np.repeat(np.repeat(eye(3, dtype=bool), 40, axis=0), 40, axis=1)
zm = ma.masked_where(~z4, ones((120,120)))
imshow(z3, cmap=cm.jet)
imshow(zm, cmap=cm.bwr, alpha=.3, vmin=0, vmax=1) #cm.bwr is an easy way to get red
# do this by changing alpha for each pixel
figure()
z5 = zeros((120, 120, 4), dtype=float)
z5[..., 0] = 1
z5[..., 3] = .4*z4.astype(float)
imshow(z3, cmap=cm.jet)
imshow(z5)
show()
I think both approaches can produce the same results for all cases, but:
1. the masked arrays can be a more direct approach if the mask or composition becomes complicated, and masking gives you more flexibility in drawing your overlay image since, for example, you can use colormaps rather than specifying the full RGBA for every pixel, but,
2. the masked array approach doesn't give full pixel-by-pixel control over the alpha value like RGBA does.
z1 = sin(X*Y)
z1 = cos(2*X)
z2 = cos(5*(X+Y))
zm = ma.masked_where( (z2<.5) & (Y>0), z1)
figure()
imshow(z3)
imshow(zm, cmap=cm.gray, alpha=.4, vmin=-2, vmax=2)
show()
It's a bit crazy, but here's what's going on: The primary image is a circular pattern that goes from blue to red (z3). Then there are vertical bars that faintly shade this (z1) but only in half of the figure and in narrow alternate diagonal bands on the other half (due to the mask). Here's a more complicated image using masked arrays:
Just to add on to what tom10 has posted, the masked arrays do work great with colormaps, but I also wrote a small function in the meantime that should work with any RGB color tuple.
def overlayImage(im, mask, col, alpha):
maskRGB = np.tile(mask[..., np.newaxis], 3)
untocuhed = (maskRGB == False) * im
overlayComponent = alpha * np.array(col) * maskRGB
origImageComponent = (1 - alpha) * maskRGB * im
return untocuhed + overlayComponent + origImageComponent
im is the rgb image
mask is a boolean mask of the image, such that mask.shape + (3,) = im.shape
col is just the 3-tuple rgb value you want to mask the image with
alpha is just the alpha value / transparency for the mask
I also needed a clear contour on my areas. Thus, you can easily add a contour plot on top: e.g., create a dummy numpy array and set a different value in each area of interest.
Here's an example build on top of tom10's answer with a different condition:
x = y = linspace(-6, 6, 100)
X, Y = meshgrid(x, y)
z3 = X*X + Y*Y # circular pattern
# first, do this with a masked array
figure()
imshow(z3, cmap=cm.jet, extent = (-6,6,-6,6));
zm = ma.masked_where((z3>=0.7) & (z3<=1.5), ones(np.shape(z3)));
imshow(zm, cmap=cm.bwr, alpha=.4, vmin=0, vmax=1, extent = (-6,6,-6,6)) #cm.bwr is an easy way to get red
# Build dummy array of 1s and 0s (you can play with different values to obtain different contours for different regions):
temp_vector = ones(np.shape(z3));
temp_vector[(z3>=0.7) & (z3<=1.5)] = 0.0;
temp_vector[(z3>8.2)] = 2.0; # etc.
# Create contour. I found only one contour necessary:
contour(X, Y, temp_vector, 1, colors=['r','g']);
show()
Which yields:

Apply complex transformation to an image using matplotlib and numpy

Hi I am trying to apply the mobius transformation to an image using matplotlib. This is python code to do this.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from numpy import *
img = mpimg.imread('test.jpg') # load an image
zi = [766j, 512+766j, 256+192j]
wi = [738j, 512+496j, 256+173j]
r = ones((600,700,3),dtype=uint8)*255 # empty-white image
for i in range(img.shape[1]):
for j in range(img.shape[0]):
z = complex(i,j)
qf = ((wi[0] * (-wi[1] * (zi[0]-zi[1]) * (z-zi[2]) + wi[2] * (z-zi[1]) * (zi[0]-zi[2])) - wi[1]*wi[2]*(z-zi[0]) * (zi[1]-zi[2])))
qs = (wi[2]*(zi[0]-zi[1])*(z-zi[2])-wi[1]*(z-zi[1])*(zi[0]-zi[2])+wi[0]*(z-zi[0])*(zi[1]-zi[2]))
w = qf/qs
r[int(imag(w)),int(real(w)),:] = img[j,i,:]
plt.subplot(121)
plt.imshow(img,origin='lower',aspect='auto')
plt.subplot(122)
plt.imshow(r,origin='lower',aspect='auto')
plt.show()
if I run this code, I get the following result.
If you see the right side, the size is changed. I want to know the way to fit the result image in the box. The way I did is I hard code the result image size and run the code. However, since the mobius transformation expands and shrink the image, sometimes I get very small image and sometimes I get very big image. Anyone can solve this problem??Thanks!
You can do the following to find the x limits and y limits of your transformed image:
plt.gca().set_aspect('equal')
i, j = np.where(np.all(r!=255, axis=2))
xlimits = j.min(), j.max()
ylimits = i.min(), i.max()
plt.xlim(xlimits)
plt.ylim(ylimits)
the set_aspect() was added to show the image in its original aspect ratio. numpy.where() will find the row and column indices where the image is not white (255, 255, 255), it is taking the minimum and maximum indices to set the new limits.

Categories

Resources