I am having an issue using the morphology functions in OpenCV on xarray data with python.
I have generated a list of binary xarray datarrays (I understand that I probably should make these just another dimension of the dataset, but I haven't gotten there yet).
I am trying to run morphological closing on this data. In the code snippet below, I extract the actual data from the datarray as a numpy array, then try to run the closing on that. My understanding of OpenCV is that when it reads an image in, it essentially translates it into a numpy array anyway, so I thought this might work. I should mention, the data is originally a geotiff, and I am reading it in using rioxarray, if that makes a difference.
Essentially, what seems to be happening is that the data is being shifted by the size of the kernel for each iteration. I have confirmed this by reading the same slice of the data in the array before and after running the closing operation. It also throws an error that "Axis Limits cannot be NaN or Inf". The output numpy array is the same size as the original, but parts of it have been cut off in one corner, and the other corner, it appears that a No Data value has been added(?) (value is -1.798e+308).
I am not sure why this is doing this. When I read an image in using imread, the same process seems to work just fine from what I can tell. I also cannot tell if the closing operation is even doing what it is supposed to be doing either. First glance, it only seems like it is shifting it. Code and image below.
import rioxarray as rxr
import xarray as xr
import cv2 as cv
import numpy as np
kSize = 15 #Kernel size
iters = 2 #number of iterations
#Binary list is a list of several binary images generated using xarray.where function
binaryCopy = binaryList[0].copy() #Copy one datarray from list of datarrays...prob should just put this all into one xr dataset, but oh well
inAttrs = binaryCopy.attrs.copy() #Copy attributes to read back in at the end
inDims = binaryCopy.dims
inCoords = binaryCopy.coords
kern = cv.getStructuringElement(cv.MORPH_RECT,(kSize,kSize)) #Create square kernel
print(binaryCopy.data.shape) #Print shape of data array (appears to just be a numpy array)
#I had tried this v first, but it did not work, so I tried the steps individually...same issue
#closed = cv.morphologyEx(binaryCopy.data, cv.MORPH_CLOSE, kern)
dilated = cv.dilate(binaryCopy.data, kern, iters)
closed = cv.erode(dilated, kern, iters)
newBinaryArray= xr.DataArray(closed,
coords=inCoords,
dims=inDims,
attrs=inAttrs)
fig, ax = plt.subplots(nrows=1, ncols=2, sharey=True)
#Plot the original first
binaryList[0].plot(ax=ax[0])
#Plot the closed data
newBinaryArray.plot(ax=ax[1])
plt.rcParams['figure.figsize'] = (15,8)
Before (left) and after (right) I run morphological closing. Notice the blue bar on the bottom and left of the image to right. This appears to be a no data value (-1.798e+308)
Ok, so it looks like my issue was the anchor. If you set argument anchor=(0,0), that seems to prevent the shift in the data. (I thought this should be (-1,-1) since other places on the internet seem to indicate that puts the anchor in the middle, but the (0,0) appears to work better for me.
Also, when I added borderType=cv.BORDER_REPLICATE (see here) as an argument to the morphologEx function (I just did it with that one instead of doing dilation and erosion), that prevented the extra strip that was inserted when the data was shifted from being an enormous "No Data" value and uses the data value at the border.
Related
I would like to voxelise a .stl file and write it into an np.array. The resolution of the voxels should be adjustable.
Here is my code for this:
component_path = r"C:\Users\User\documents\components\Test_1.stl"
mesh = o3d.io.read_triangle_mesh(component_path)
voxel_grid = o3d.geometry.VoxelGrid.create_from_triangle_mesh(mesh, voxel_size = 3)
ply_path = "voxel.ply"
o3d.io.write_voxel_grid(ply_path, voxel_grid, True,True,True)
pcd = o3d.io.read_point_cloud(ply_path)
list_path = "list.xyz"
o3d.io.write_point_cloud(list_path, pcd)
Then I read the coordinate points from the list, write them into a 3D array and plot them. When plotting, the border is not displayed for certain voxel sizes, as can be seen in the image (although it is present in the original). Is there a solution for this so that it doesn't happen no matter what voxel size?
voxelized picture with missing border
In addition, the voxel size changes the maximum dimension. So the component originally has three times the length as it is shown here. How can this be adjusted? (If I just multiply a factor, the voxels stay small but pull the distances apart).
Is there perhaps a more reasonable way to write a voxelisation of a .stl file and put the centers of voxels into an np.array?
If anyone ever has the same problem and is looking for a solution:
This project worked for me: GitHub: stl-to-voxel
The model is then also filled. If the maximum dimension is known, you can determine the exact voxel size via the resolution.
Here is some code:
import stl_reader
import stltovoxel
import numpy as np
import copy
import os
import sys
input=r"C:\Users\user\Example.stl"
output=r"C:\Users\user\Test.xyz"
resolution = 50 #Resolution, into how many layers the model should be divided
stltovoxel.doExport(input, output, resolution)
Afterwards, you can read the coordinates from the list, write them into an array and process them further (quite normally).
I'm looking for a library that enables to "create pictures" (or even videos) with the following functions:
Accepting picture inputs
Resizing said inputs to fit given template / scheme
Positioning the pictures in pre-set up layers or coordinates
A rather schematic approach to look at this:
whereas the red spots are supposed to represent e.g. text, picture (or if possible video) elements.
The end goal would be to give the .py script multiple input pictures and the .py creating a finished version like mentioned above.
Solutions I tried were looking into Python PIL, but I wasn't able to find what I was looking for.
Yes, it is possible to do this with Python.
The library you are looking for is OpenCV([https://opencv.org][1]/).
Some basic OpenCV python tutorials (https://docs.opencv.org/master/d9/df8/tutorial_root.html).
1) You can use imread() function to read images from files.
2) You can use resize() function to resize the images.
3) You can create a empty master numpy array matching the size and depth(color depth) of the black rectangle in the figure you have shown, resize your image and copy the contents into the empty array starting from the position you want.
Below is a sample code which does something close to what you might need, you can modify this to suit your actual needs. (Since your requirements are not clear I have written the code like this so that it can at least guide you.)
import numpy as np
import cv2
import matplotlib.pyplot as plt
# You can store most of these values in another file and load them.
# You can modify this to set the dimensions of the background image.
BG_IMAGE_WIDTH = 100
BG_IMAGE_HEIGHT = 100
BG_IMAGE_COLOR_DEPTH = 3
# This will act as the black bounding box you have shown in your figure.
# You can also load another image instead of creating empty background image.
empty_background_image = np.zeros(
(BG_IMAGE_HEIGHT, BG_IMAGE_WIDTH, BG_IMAGE_COLOR_DEPTH),
dtype=np.int
)
# Loading an image.
# This will be copied later into one of those red boxes you have shown.
IMAGE_PATH = "./image1.jpg"
foreground_image = cv2.imread(IMAGE_PATH)
# Setting the resize target and top left position with respect to bg image.
X_POS = 4
Y_POS = 10
RESIZE_TARGET_WIDTH = 30
RESIZE_TARGET_HEIGHT = 30
# Resizing
foreground_image= cv2.resize(
src=foreground_image,
dsize=(RESIZE_TARGET_WIDTH, RESIZE_TARGET_HEIGHT),
)
# Copying this into background image
empty_background_image[
Y_POS: Y_POS + RESIZE_TARGET_HEIGHT,
X_POS: X_POS + RESIZE_TARGET_WIDTH
] = foreground_image
plt.imshow(empty_background_image)
plt.show()
i want to load a mesh file (.obj), then want to use the trimesh.sample.sample_surface_even() function to get some points on the surface, turn the resulting points back into a mesh and save them back as an .obj file.
My problem is, that i dont know how to turn the samples back into a mesh that can be saved. Can somebody tell me what i should do step by step, to achieve that goal?
Here is my code so far:
import numpy as np
import trimesh
mesh = trimesh.load_mesh('mesh10.obj')
sampledmesh= trimesh.sample.sample_surface_even(mesh,500)
#? How to turn sampledmesh back into a mesh?
sampledmesh.export('mesh10_export.obj')
You can use the submesh function on the sampled face indices, which is the second element in the returned tuple:
sampledmesh = trimesh.sample.sample_surface_even(mesh,500)
sampled_submesh = mesh.submesh([sampledmesh[1]])[0]
submesh returns an array of meshes, but here we just have one, so we take the first mesh.
I am reading VTK uniform grid into python. When I visualize a slice through the data in Paraview, I get the following (correct) image:
Then I visualize the slice using via numpy & pylab using the following script:
import vtk
from vtk.util.numpy_support import vtk_to_numpy
import pylab
imr=vtk.vtkXMLImageDataReader()
imr.SetFileName('flow.vti')
imr.Update()
im=imr.GetOutput()
nx,ny,nz=im.GetDimensions()
orig=im.GetOrigin()
extent=im.GetExtent()
spacing=im.GetSpacing()
flowVtk=im.GetPointData().GetArray("|flow|")
flow=vtk_to_numpy(flowVtk).reshape(nx,ny,nz)
# bottom z-slice
flowZ0=flow[:,:,0]
# set extent so that axes units are physical
img=pylab.imshow(flowZ0,extent=[orig[0],orig[0]+extent[1]*spacing[0],orig[1],orig[1]+extent[3]*spacing[1]],cmap=pylab.gray())
img.set_clim(vmin=0,vmax=1000)
pylab.show()
which seems to be out-of-phase. I tried reordering dimensions in reshape(...), it did something, but it has never shown the data it is actually supposed to show.
Is there something obviously wrong?
EDIT: I also tried reshape((nx,ny,nz),order="F") (fortran ordering) and now I get a much better image (with jet colormap for better clarity) which is almost correct, but the data is suspiciously rotated by 90°, plus I would like some authoritative explanation which ordering to use and why (which one is used by VTK internally?).
EDIT2: to get the same view as in Paraview, I had to do pylab.imshow(np.rot90(flowZ0)); not sure why, so the question is still open:
I'm writing a code that part of it is reading an image source and displaying it on the screen for the user to interact with. I also need the sharpened image data. I use the following to read the data and display it in pyGame
def image_and_sharpen_array(file_name):
#read the image data and return it, with the sharpened image
image = misc.imread(file_name)
blurred = ndimage.gaussian_filter(image,3)
edge = ndimage.gaussian_filter(blurred,1)
alpha = 20
out = blurred + alpha*(blurred - edge)
return image,out
#get image data
scan,sharpen = image_and_sharpen_array('foo.jpg')
w,h,c = scan.shape
#setting up pygame
pygame.init()
screen = pygame.display.set_mode((w,h))
pygame.surfarray.blit_array(screen,scan)
pygame.display.update()
And the image is displayed on the screen only rotated and inverted. Is this due to differences between misc.imread and pyGame? Or is this due to something wrong in my code?
Is there other way to do this? The majority of solution I read involved saving the figure and then reading it with ``pyGame''.
I often use the numpy swapaxes() method:
In this case we only need to invert x and y axis (axis number 0 and 1) before displaying our array :
return image.swapaxes(0,1),out
I thought technico provided a good solution - just a little lean on info. Assuming get_arr() is a function that returns the pixel array:
pixl_arr = get_arr()
pixl_arr = numpy.swapaxes(pixl_arr, 0, 1)
new_surf = pygame.pixelcopy.make_surface(pixl_arr)
screen.blit(new_surf, (dest_x, dest_y))
Alternatively, if you know that the image will always be of the same dimensions (as in iterating through frames of a video or gif file), it would be more efficient to reuse the same surface:
pixl_arr = get_arr()
pixl_arr = numpy.swapaxes(pixl_arr, 0, 1)
pygame.pixelcopy.array_to_surface(old_surf, pixl_arr)
screen.blit(old_surf, (dest_x, dest_y))
YMMV, but so far this is working well for me.
Every lib has its own way of interpreting image arrays. By 'rotated' I suppose you mean transposed. That's the way PyGame shows up numpy arrays. There are many ways to make it look 'correct'. Actually there are many ways even to show up the array, which gives you full control over channel representation and so on. In pygame version 1.9.2, this is the fastest array rendering that I could ever achieve. (Note for earlier version this will not work!).
This function will fill the surface with array:
def put_array(surface, myarr): # put array into surface
bv = surface.get_view("0")
bv.write(myarr.tostring())
If that is not working, use this, should work everywhere:
# put array data into a pygame surface
def put_arr(surface, myarr):
bv = surface.get_buffer()
bv.write(myarr.tostring(), 0)
You probably still get not what you want, so it is transposed or have swapped color channels. The idea is, manage your arrays in that form, which suites this surface buffer. To find out what is correct channel order and axes order, use openCV library (cv2.imread(filename)). With openCV you open images in BGR order as standard, and it has a lot of conversion functions. If I remember correctly, when writing directly to surface buffer, BGR is the correct order for 24 bit and BGRA for a 32 bit surface. So you can try to put the image array which you get out of file with this function and blit to the screen.
There are other ways to draw arrays e.g. here is whole set of helper functions http://www.pygame.org/docs/ref/surfarray.html
But I would not recommend using it, since surfaces are not for direct pixel manipulating, you will probably get lost in references.
Small tip: To do 'signalling test' use a picture, like this. So you will immediately see if something is wrong, just load as array and try to render.
My suggestion is to use the pygame.transform module. There are the flip and rotate methods, which you can use to however your transformation is. Look up the docs on this.
My recommendation is to save the output image to a new Surface, and then apply the transformations, and blit to the display.
temp_surf = pygame.Surface((w,h))
pygame.surfarray.blit(temp_surf, scan)
'''transform temp_surf'''
screen.blit(temp_surf, (0,0))
I have no idea why this is. It is probably something to do with the order in which the axes are transferred from a 2d array to a pygame Surface.