Trying to make alienrain in python using the opengl function glMapBufferRange - python

Just 4 little lines causing a problem with the alien rain program that I ported from the OpenGL Superbible. It seems I am having issues trying to write to memory after using the function glMapBufferRange
Update: Excellent code by Rabbid76 has solved the problem and provided valuable insight of explanation. Thank You.
Required files: ktxloader.py , aliens.ktx
Source code of alienrain.py
#!/usr/bin/python3
import sys
import time
sys.path.append("./shared")
#from sbmloader import SBMObject # location of sbm file format loader
from ktxloader import KTXObject # location of ktx file format loader
#from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44
fullscreen = True
import numpy.matlib
import numpy as np
import math
try:
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray
except:
print ('''
ERROR: PyOpenGL not installed properly.
''')
sys.exit()
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]
render_prog = GLuint(0)
render_vao = GLuint(0)
tex_alien_array = GLuint(0)
rain_buffer = GLuint(0)
droplet_x_offset = []
droplet_rot_speed = []
droplet_fall_speed = []
seed = 0x13371337
import random
import ctypes
random.seed (0x13371337)
def random_float():
# global seed
# res=0.0
# tmp=0
# seed *= 16807;
# tmp = seed ^ (seed >> 4) ^ (seed << 15);
# res = (tmp >> 9) | 0x3F800000;
# return (res - 1.0);
return (random.random() - 1.0)
class Scene:
def __init__(self, width, height):
global render_prog
global render_vao
global tex_alien_array
global rain_buffer
global droplet_x_offset, droplet_rot_speed, droplet_fall_speed
self.width = width
self.height = height
vs = GLuint(0)
fs = GLuint(0)
vs_source = '''
#version 410 core
layout (location = 0) in int alien_index;
out VS_OUT
{
flat int alien;
vec2 tc;
} vs_out;
struct droplet_t
{
float x_offset;
float y_offset;
float orientation;
float unused;
};
layout (std140) uniform droplets
{
droplet_t droplet[256];
};
void main(void)
{
const vec2[4] position = vec2[4](vec2(-0.5, -0.5),
vec2( 0.5, -0.5),
vec2(-0.5, 0.5),
vec2( 0.5, 0.5));
vs_out.tc = position[gl_VertexID].xy + vec2(0.5);
float co = cos(droplet[alien_index].orientation);
float so = sin(droplet[alien_index].orientation);
mat2 rot = mat2(vec2(co, so),
vec2(-so, co));
vec2 pos = 0.25 * rot * position[gl_VertexID];
gl_Position = vec4(pos.x + droplet[alien_index].x_offset,
pos.y + droplet[alien_index].y_offset,
0.5, 1.0);
vs_out.alien = alien_index % 64;
}
'''
fs_source = '''
#version 410 core
layout (location = 0) out vec4 color;
in VS_OUT
{
flat int alien;
vec2 tc;
} fs_in;
uniform sampler2DArray tex_aliens;
void main(void)
{
color = texture(tex_aliens, vec3(fs_in.tc, float(fs_in.alien)));
}
'''
vs = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vs, vs_source)
glCompileShader(vs)
glGetShaderInfoLog(vs)
fs = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fs, fs_source)
glCompileShader(fs)
glGetShaderInfoLog(vs)
render_prog = glCreateProgram()
glAttachShader(render_prog, vs)
glAttachShader(render_prog, fs)
glLinkProgram(render_prog)
glDeleteShader(vs)
glDeleteShader(fs)
glGetProgramInfoLog(render_prog)
glGenVertexArrays(1, render_vao)
glBindVertexArray(render_vao)
ktxobj = KTXObject()
tex_alien_array = ktxobj.ktx_load("aliens.ktx")
glBindTexture(GL_TEXTURE_2D_ARRAY, tex_alien_array)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glGenBuffers(1, rain_buffer)
glBindBuffer(GL_UNIFORM_BUFFER, rain_buffer)
glBufferData(GL_UNIFORM_BUFFER, 256*4*4, None, GL_DYNAMIC_DRAW)
for i in range(0, 256):
droplet_x_offset.append(random_float() * 2.0 - 1.0)
droplet_rot_speed.append( (random_float() + 0.5) * (-3.0 if (i & 1) else 3.0) )
droplet_fall_speed.append ( random_float() + 0.2 )
glBindVertexArray(render_vao);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
def display(self):
global rain_buffer
green = [ 0.0, 0.1, 0.0, 0.0 ]
currentTime = time.time()
t = currentTime
glViewport(0, 0, self.width, self.height)
glClearBufferfv(GL_COLOR, 0, green)
glUseProgram(render_prog);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, rain_buffer);
droplet = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256*4*4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
float_array = ((ctypes.c_float * 4) * 256).from_address(droplet)
for i in range(0, 256):
float_array[i][0] = droplet_x_offset[i] + 2
float_array[i][1] = 2.0-math.fmod((t + float(i)) * droplet_fall_speed[i], 4.31 ) * random_float()
float_array[i][2] = droplet_rot_speed[i] * t * random_float() * math.pi
float_array[i][3] = 0.0
glUnmapBuffer(GL_UNIFORM_BUFFER);
for alien_index in range(0, 256):
glVertexAttribI1i(0, alien_index);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glutSwapBuffers()
def reshape(self, width, height):
self.width = width
self.height = height
def keyboard(self, key, x, y ):
global fullscreen
global many_cubes
print ('key:' , key)
if key == b'\x1b': # ESC
sys.exit()
elif key == b'f' or key == b'F': #fullscreen toggle
if (fullscreen == True):
glutReshapeWindow(512, 512)
glutPositionWindow(int((1360/2)-(512/2)), int((768/2)-(512/2)))
fullscreen = False
else:
glutFullScreen()
fullscreen = True
print('done')
def init(self):
pass
def timer(self, blah):
glutPostRedisplay()
glutTimerFunc( int(1/60), self.timer, 0)
time.sleep(1/60.0)
if __name__ == '__main__':
start = time.time()
glutInit()
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)
glutInitWindowSize(512, 512)
w1 = glutCreateWindow('OpenGL SuperBible - Alien Rain')
glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))
fullscreen = False
many_cubes = False
#glutFullScreen()
scene = Scene(512,512)
glutReshapeFunc(scene.reshape)
glutDisplayFunc(scene.display)
glutKeyboardFunc(scene.keyboard)
glutIdleFunc(scene.display)
#glutTimerFunc( int(1/60), scene.timer, 0)
scene.init()
glutMainLoop()
Current output is:
Update: very similar to expected output. Except it very fast and the rotations of each alien are not as smooth as the expected output. If anyone wants to tinker with the values to get it, please do. Otherwise this question is answered.
The output should be:
Here's the alienrain.cpp that was used to create alienrain.py

First of all, the 2nd parameter of glBufferData is the buffer data in bytes.
Since the glsl data structure is
struct droplet_t
{
float x_offset;
float y_offset;
float orientation;
float unused;
};
layout (std140) uniform droplets
{
droplet_t droplet[256];
};
the buffer size is 4*4*256, because the size of a float is 4, t he structure has 4 elements of type flaot and the array has 256 elements
glBufferData(GL_UNIFORM_BUFFER, 256*4*4, None, GL_DYNAMIC_DRAW)
The instruction
droplet = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256*4*4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
returns a pointer to a allocated memory region with the proper size. You've to fill this memory with data.
The easiest solution is to use the (in python built-in) library ctypes, which has the handy function .from_address():
This method returns a ctypes type instance using the memory specified by address.
so the instruction
float_array = ((ctypes.c_float * 4) * 256).from_address(droplet)
"wraps" a 2 dimensional array to the memory addressed by droplet, with 256 elements and each element has 4 elements of type float.
The values can be set to the array, by a simple assignment statement. e.g.:
import random
import math
import ctypes
glBindBufferBase(GL_UNIFORM_BUFFER, 0, rain_buffer);
droplet = glMapBufferRange(GL_UNIFORM_BUFFER, 0, 256*4*4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)
float_array = ((ctypes.c_float * 4) * 256).from_address(droplet)
for i in range(0, 256):
float_array[i][0] = random.random() * 2 -1
float_array[i][1] = random.random() * 2 -1
float_array[i][2] = random.random() * math.pi * 2
float_array[i][3] = 0.0

Related

Adding a text overlay at angle with PIL function

Starting with this
The desired effect would be any of the scenarios depicted below:
However, I can't get past this error: images do not match from the image.paste call
I'm sure there are other ways you could create this effect but it felt the cleanest to just make a new layer and paste that layer onto the original image
round() is used as I was receiving errors int is the expected argument, received float without it them.
from PIL import Image, ImageOps, ImageFont, ImageDraw
def addText( img: Image, text: str, position: dict = {}):
"""
example position dict:
{
"x": 0.79,
"y": 0.17,
"angle": 0.06
}
"""
i_w, i_h = img.size
x = round(i_w * position.get("x", 0.23))
y = round(i_h * position.get("y", 0.87))
angle = (
position.get("angle", 0) * 180
) # converts unit radians to degrees i.e. { 1 unit = pi radians= 180 degrees }
font_px_height = round(i_h * 0.0475)
font = ImageFont.truetype("fonts/OpenSans-Semibold.ttf", font_px_height)
t_w, t_h = font.getsize(text)
print(f"font.size {t_w} {t_h}")
layer = Image.new("RGBA", (round(t_w * 1.05), round(t_h)))
draw_layer = ImageDraw.Draw(layer)
draw_layer.rounded_rectangle(
(0, 0, round(t_w * 1.05), round(t_h)),
round(t_h / 10)
)
draw_layer.text((0, 0), text, fill=(0, 0, 0), font=font)
layer = layer.rotate(angle, expand=1)
l_w, l_h = layer.size
l_w = round(l_w)
l_h = round(l_h)
layer.show()
img.paste(
sticker,
(
round(x - l_w / 2),
round(y - l_h / 2),
round(x + l_w / 2),
round(y + l_h / 2),
),
sticker,
)
return img
try:
with Image.open(
"./test.jpg"
) as im:
alt_im = addText(
im,
"hello world",
{"x": 0.23, "y": 0.87, "angle": 0.06},
)
alt_im.save("out.jpg")
except Exception as ex:
print("*Exception output", ex)
So I ended up using a bodge, to work with only equal numbers during the calculations so they would work out as ints each time. Removing all mention of round() and replacing it with evenize()
def evenize(*args):
evenized_numbers = []
for _n in args:
n = int(_n)
if (n % 2) != 0:
n += 1
evenized_numbers.append(n)
if len(evenized_numbers) > 1:
return tuple(evenized_numbers)
else:
return evenized_numbers[0]
works a charm

Ctypes Cuda - pointer multiplication does not result in product

I implemented a Cuda matrix multiplication solely in C which successfully runs. Now I am trying to shift the Matrix initialization to numpy and use Python's ctypes library to execute the c code. It seems like the array with the pointer does not contain the multiplied values. I am not quite sure where the problem lies, but already in the CUDA code - even after calling the Kernel method and loading back the values from device to host, values are still zeroes.
The CUDA code:
#include <stdio.h>
#include <stdlib.h>
#define BLOCK_SIZE 16
#define RANDOM_MN_RANGE 64
struct Matrix {
int width;
int height;
// contiguously stored Matrix, in row first order
float *elements;
};
__global__ void MatMulKernel(Matrix A, Matrix B, Matrix C){
// runs for each col - row pair
float tmpVal = 0;
int col = blockIdx.x * blockDim.x + threadIdx.x;
int row = blockIdx.y * blockDim.y + threadIdx.y;
for (int i = 0; i < A.width; ++i)
tmpVal += A.elements[row * A.width + i] *
B.elements[i * B.width + col];
C.elements[ row * C.width + col ] = tmpVal;
}
extern "C" {
void mMul( Matrix *A, Matrix *B, Matrix *C ){
Matrix d_A, d_B, d_C;
// Matrix d_A
d_A.width = A->width;
d_A.height = A->height;
size_t sizeA = A->width * A->height * sizeof(float);
// dynamically allocate cudaMemory for elemenst array
cudaMalloc(&d_A.elements, sizeA);
cudaMemcpy(d_A.elements, A->elements, sizeA, cudaMemcpyHostToDevice);
// Matrix d_B
d_B.width = B->width;
d_B.height = B->height;
size_t sizeB = B->width * B->height * sizeof(float);
// dynamically allocate cudaMemory for elemenst array
cudaMalloc(&d_B.elements, sizeB);
cudaMemcpy(d_B.elements, B->elements, sizeB, cudaMemcpyHostToDevice);
// Matrix d_C
d_C.width = C->width;
d_C.height = C->height;
size_t sizeC = C->width * C->height * sizeof(float);
// dynamically allocate cudaMemory for elemenst array
cudaMalloc(&d_C.elements, sizeC);
// 16 * 16 = 256 threads per block
dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
// Blocks per grid
dim3 dimGrid(B->width / dimBlock.x, A->height / dimBlock.y);
// calling the Kernel
MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C);
// copy results from result matrix C to the host again
cudaMemcpy(C->elements, d_C.elements, sizeC, cudaMemcpyDeviceToHost);
// C->elements[0] contains still 0, the values do not seem to be loaded back to host memory.
printf("A is %f\n", A->elements[0]);
printf("B is %f\n", B->elements[0]);
printf("C is %f\n", C->elements[0]);
// free the cuda memory
cudaFree(d_A.elements);
cudaFree(d_B.elements);
cudaFree(d_C.elements);
}
}
To compile the code I use the following command
nvcc --shared --compiler-options '-fPIC' -o Sequential_Cuda_Python.so Sequential_Cuda_Python.cu
ctypes Python code
import numpy as np
from numpy.ctypeslib import ndpointer
from ctypes import *
class Matrix(Structure):
_fields_ = [("width", c_int),
("height", c_int),
("elements", POINTER(c_float))]
libc = CDLL("./Sequential_Cuda_Python.so")
libc.mMul.argtypes = [ POINTER(Matrix), POINTER(Matrix), POINTER(Matrix) ]
libc.mMul.restype = None
def npArrtoMatrixPtr(data: np.ndarray) -> (POINTER(Matrix), tuple):
"""
numpy arr to Matrix pointer
#return (pointer to arr, shape)
"""
c_float_p = POINTER(c_float)
data = data.astype(np.float32)
w, h = np.shape(data)
# print((w, h))
mXp = Matrix(height=h, width=w, elements=data.ctypes.data_as(c_float_p))
return (pointer(mXp), np.shape(data))
def matMulSeqCuda( _mA, _mB, _mC ):
"""
multiplies mA with mB sequentually using mC
"""
pmA, mASz = ( _mA[0], _mA[-1] )
pmB, mBSz = ( _mB[0], _mB[-1] )
pmC, mCSz = ( _mC[0], _mC[-1] )
assert np.shape( mASz )[0] == 2 and \
np.shape( mBSz )[0] == 2 and \
np.shape( mCSz )[0] == 2, "Only 2D arrays accepted"
#assert np.shape(mA)[0] == np.shape(mB)[1], "Rows of mA need to be the same as Cols of mB"
libc.mMul( pmA, pmB, pmC )
c = pmC.contents
mtxC = np.ctypeslib.as_array(c.elements, shape=(c.height, c.height))
# The array still contains only 0. values
print(mtxC)
return 0
if __name__ == '__main__':
a = np.ones( (16, 8) )
b = np.ones( (16, 8) )
c = np.zeros( (16, 16) )
mA = npArrtoMatrixPtr(a)
mB = npArrtoMatrixPtr(b)
mC = npArrtoMatrixPtr(c)
matMulSeqCuda(mA, mB, mC)
Solution:
As #Mark Tolonen pointed out the error lied in the Python script. By calling npArrtoMatrixPptr(creates and returns a Pointer to a Matrix struct) within the same scope as the CUDA function libc.mMul I was able to retrieve the correct resulting Matrix mtxC.
import numpy as np
from numpy.ctypeslib import ndpointer
from ctypes import *
class Matrix(Structure):
_fields_ = [("width", c_int),
("height", c_int),
("elements", POINTER(c_float))]
libc = CDLL("./Sequential_Cuda_Python.so")
libc.mMul.argtypes = [ POINTER(Matrix), POINTER(Matrix), POINTER(Matrix) ]
libc.mMul.restype = None
def npArrtoMatrixPtr(data: np.ndarray) -> (POINTER(Matrix), tuple):
"""
numpy arr to Matrix pointer
#return (pointer to arr, shape)
"""
#c_float_p = POINTER(c_float)
data = data.astype(np.float32)
h, w = data.shape
mXp = Matrix(w, h, data.ctypes.data_as(POINTER(c_float)))
return (pointer(mXp), np.shape(data))
def matMulSeqCuda( npMa, npMb, npMc ):
"""
multiplies mA with mB sequentually using mC
"""
assert len(np.shape( npMa )) == 2 and \
len(np.shape( npMb )) == 2 and \
len(np.shape( npMc )) == 2, "Only 2D arrays accepted"
pmA, szA = npArrtoMatrixPtr(npMa)
pmB, szB = npArrtoMatrixPtr(npMb.T)
pmC, szC = npArrtoMatrixPtr(npMc) # the resulting array
libc.mMul( pmA, pmB, pmC )
c = pmC.contents
mtxC = np.ctypeslib.as_array(c.elements, shape=(c.height, c.width))
# the result is correct
print(mtxC)
return 0
if __name__ == '__main__':
a = np.ones( (16, 8) )
b = np.ones( (16, 8) )
c = np.zeros( (16, 16) )
matMulSeqCuda(a, b, c)
I can't compile your code as is, but the problem is that np.shape returns (rows,columns) or the equivalent (height,width), not (width,height):
w, h = np.shape(data) # should be h,w
And also on visual inspection this line is wrong (c.height twice).
mtxC = np.ctypeslib.as_array(c.elements, shape=(c.height, c.height))
The CUDA code is extraneous to the question, which is passing a Matrix* correctly and receiving back the modifications. I made a minimal reproducible example below that concentrates on passing a Matrix correctly:
test.cpp - receives a Matrix structure and doubles the values in it.
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
struct Matrix {
int width;
int height;
float *elements;
};
extern "C" API
void doubleit(Matrix *A) {
for(int r = 0; r < A->height; ++r)
for(int c = 0; c < A->width; ++c) {
A->elements[r * A->width + c] *= 2;
}
}
test.py
import numpy as np
from ctypes import *
class Matrix(Structure):
_fields_ = [("width", c_int),
("height", c_int),
("elements", POINTER(c_float))]
libc = CDLL('./test')
libc.doubleit.argtypes = POINTER(Matrix),
libc.doubleit.restype = None
def doubleit(a):
h,w = a.shape # note h,w not w,h
m = Matrix(w,h,a.ctypes.data_as(POINTER(c_float)))
libc.doubleit(m)
a = np.arange(0.0,0.6,0.1,dtype=np.float32).reshape((2,3))
print(a)
doubleit(a)
print(a)
Output:
[[0. 0.1 0.2]
[0.3 0.4 0.5]]
[[0. 0.2 0.4]
[0.6 0.8 1. ]]

Why does translation matrix not affect rendering of 3d model in python?

I am very new to coding and decided to start with a 3D engine in python. I followed OLC's tutorial and adapted it for python. However, my translation matrix doesn't seem to be affecting how the model is rendered. Any advice at all, from code improvements, to telling me how bad I am would be much appreciated.
I have tried to re-do the vector multiplication in all sorts of ways, and all kinds of reformatting and re-trying, but I am not even sure what I am looking for in this case. Any help would be much appreciated
As I am new, I apologize for any poor etiquette I may have done on this site.
import math, numpy as np, pygame, sys, os, random, string
from pygame.locals import *
pygame.init()
width,height = 1600,900;cx,cy = width//2,height//2
screen = pygame.display.set_mode((width,height))
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
pygame.display.set_caption('3D Graphics')
def loadObj(filename):
tris = []
verts = []
try:
fp = open(filename, "r")
except:
print("File: "+filename+" not found")
sys.exit(1)
for line in fp:
if line.startswith('#'): continue
values = line.split()
if not values: continue
if values[0] == 'v':
#v = vec3(float(values[1]), float(values[2]), float(values[3]))
#verts.append(vec3(int(values[1]), int(values[2]), int(values[3])))
verts.append(vec3(float(values[1]), float(values[2]), float(values[3]), 0 ))
#verts.append(v)
elif values[0] == 'f':
p = []
for v in values[1:]:
w = v.split("/")
p.append(int(w[0]))
#print(p)
#print(verts[-185])
triTemp = triangle(verts[p[0] - 1], verts[p[1] - 1], verts[p[2]- 1])
tris.append(triTemp)
fp.close()
return tris
class vec3(): #Possibly Obsolete
__slots__ = ['x','y','z','w']
def __init__(self, x, y, z, w):
self.x = x
self.y = y
self.z = z
self.w = w
class triangle():
__slots__ = ['vec1','vec2','vec3']
def __init__(self, vec1, vec2, vec3):
self.vec1 = vec1
self.vec2 = vec2
self.vec3 = vec3
def matrixMakeTranslation(x, y, z):
matrix = np.zeros((4,4))
matrix[0,0] = 1.0
matrix[1,1] = 1.0
matrix[2,2] = 1.0
matrix[3,3] = 1.0
matrix[3,0] = x
matrix[3,1] = y
matrix[3,2] = z
return matrix
def Matrix_MakeRotationX(fAngleRad):
matrix = np.zeros((4,4))
matrix[0,0] = 1.0
matrix[1,1] = (math.cos(fAngleRad * 0.5))
matrix[1,2] = (math.sin(fAngleRad * 0.5))
matrix[2,1] = (-math.sin(fAngleRad * 0.5))
matrix[2,2] = (math.cos(fAngleRad * 0.5))
matrix[3,3] = 1.0
return matrix
def Matrix_MakeRotationZ(fAngleRad):
matrix = np.zeros((4,4))
matrix[0,0] = (math.cos(fAngleRad))
matrix[0,1] = (math.sin(fAngleRad))
matrix[1,0] = (-math.sin(fAngleRad))
matrix[1,1] = (math.cos(fAngleRad))
matrix[2,2] = 1.0
matrix[3,3] = 1.0
return matrix
fNear = float(0.1) #Create the Projection Matrix
fFar = float(1000.0)
fFov = float(90.0)
fAspectRatio = float(height/width)
fFovRad = 1/math.tan(fFov * 0.5 / 180 * math.pi)
projectionMatrix = np.zeros((4,4))
projectionMatrix[0,0] = fAspectRatio * fFovRad
projectionMatrix[1,1] = fFovRad
projectionMatrix[2,2] = fFar / (fFar - fNear)
projectionMatrix[3,2] = float((-fFar * fNear) / (fFar - fNear))
projectionMatrix[2,3] = 1.0
projectionMatrix[3,3] = 0.0
meshname = "teapot.obj" #Load the mesh
tris = loadObj(meshname)
vCamera = np.array([0,0,0,0])
fAngleRad = 0
colour = (255,255,255)
colour2 = (0,0,0)
triProjected = triangle(np.array([0,0,0,0]),np.array([0,0,0,0]),np.array([0,0,0,0])) #These are used later
triTranslalted = triangle(np.array([0,0,0,0]),np.array([0,0,0,0]),np.array([0,0,0,0]))
triTransformed= triangle(np.array([0,0,0,0]),np.array([0,0,0,0]),np.array([0,0,0,0]))
while True: #Begin Loop
for event in pygame.event.get(): #Quit
if event.type == pygame.QUIT: pygame.quit(); sys.exit()
dt = clock.tick()/1000
pygame.display.set_caption('3D Graphics - FPS: %.2f'%int(dt))
print("fps:", clock.get_fps()) #Framerate and caption
pygame.display.update()
screen.fill((0,0,0))
fAngleRad += 0.1
matRotZ = Matrix_MakeRotationZ(fAngleRad * 0.5) #Set up matricies
matRotX = Matrix_MakeRotationX(fAngleRad)
matTrans = matrixMakeTranslation(0.0,0.0,50.0)
matWorld = np.identity(4)
matWorld = matRotZ # matRotX
matWorld = matWorld # matTrans #Seems to be broken. idk why.
for i in tris: #For triangle in all triangles
reDo1 = np.array([i.vec1.x, i.vec1.y, i.vec1.z, i.vec1.w])
reDo2 = np.array([i.vec2.x, i.vec2.y, i.vec2.z, i.vec2.w])
reDo3 = np.array([i.vec3.x, i.vec3.y, i.vec3.z, i.vec3.w])
triTransformed.vec1 = np.matmul(matWorld, reDo1)
triTransformed.vec2 = np.matmul(matWorld, reDo2)
triTransformed.vec3 = np.matmul(matWorld, reDo3)
triProjected.vec1 = np.matmul(projectionMatrix, triTransformed.vec1)
triProjected.vec2 = np.matmul(projectionMatrix, triTransformed.vec2)
triProjected.vec3 = np.matmul(projectionMatrix, triTransformed.vec3)
#Scale Into View
triProjected.vec1[0] += 1.0
triProjected.vec1[1] += 1.0
triProjected.vec2[0] += 1.0
triProjected.vec2[1] += 1.0
triProjected.vec3[0] += 1.0
triProjected.vec3[1] += 1.0
triProjected.vec1[0] *= 0.5 * width
triProjected.vec1[1] *= 0.5 * height
triProjected.vec2[0] *= 0.5 * width
triProjected.vec2[1] *= 0.5 * height
triProjected.vec3[0] *= 0.5 * width
triProjected.vec3[1] *= 0.5 * height
pygame.draw.polygon(screen, colour, [(triProjected.vec1[0], triProjected.vec1[1]),(triProjected.vec2[0], triProjected.vec2[1]),(triProjected.vec3[0], triProjected.vec3[1])])
You are using Homogenious Coordinates to represent the vertices of your model. So the W component must be 1.
When you load your model you are setting W to 0.
verts.append(vec3(float(values[1]), float(values[2]), float(values[3]), 0 ))
By setting W=0 you are creating a Homogenious Vector (a.k.a "point at infinity" or "ideal point") and by setting W=1 you are creating a Homogeneous Point.
Points can be translated but vectors cannot.
https://en.m.wikipedia.org/wiki/Homogeneous_coordinates

PySide/python video player issue

I am trying to write a simple YUV video player using python. After some initial study, I thought I could use PySide and started with it. As a first step, I have taken the following approach without consideration for real-time performance.
Read YUV buffer (420 planar) -> convert the YUV image to RGB (32bit format) - > call PySide utilities for display. The basic problem that I have with my simple program is that I am able to get only the first frame to display and the rest are not displayed, eventhough the paint event seems to be happening according to the counter in the (below) code. I would appreciate any comments to understand
(i) any mistakes and lack of understanding from my side regarding painting/repainting at regular intervals on QLabel/QWidget.
(ii) Any pointers to Python based video players/display from YUV or RGB source.
#!/usr/bin/python
import sys
from PySide.QtCore import *
from PySide.QtGui import *
import array
import numpy as np
class VideoWin(QWidget):
def __init__(self, width, height, f_yuv):
QWidget.__init__(self)
self.width = width
self.height = height
self.f_yuv = f_yuv
self.setWindowTitle('Video Window')
self.setGeometry(10, 10, width, height)
self.display_counter = 0
self.img = QImage(width, height, QImage.Format_ARGB32)
#qApp.processEvents()
def getImageBuf(self):
return self.img.bits()
def paintEvent(self, e):
painter = QPainter(self)
self.display_counter += 1
painter.drawImage(QPoint(0, 0), self.img)
def timerSlot(self):
print "In timer"
yuv = array.array('B')
pix = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=self.getImageBuf())
for i in range(0,self.height):
for j in range(0, self.width):
pix[i, j] = 0
for k in range (0, 10):
#qApp.processEvents()
yuv.fromfile(self.f_yuv, 3*self.width*self.height/2)
for i in range(0, self.height):
for j in range(0, self.width):
Y_val = yuv[(i*self.width)+j]
U_val = yuv[self.width*self.height + ((i/2)*(self.width/2))+(j/2)]
V_val = yuv[self.width*self.height + self.width*self.height/4 + ((i/2)*(self.width/2))+(j/2)]
C = Y_val - 16
D = U_val - 128
E = V_val - 128
R = (( 298 * C + 409 * E + 128) >> 8)
G = (( 298 * C - 100 * D - 208 * E + 128) >> 8)
B = (( 298 * C + 516 * D + 128) >> 8)
if R > 255:
R = 255
if G > 255:
G = 255
if B > 255:
B = 255
assert(int(R) < 256)
pix[i, j] = (255 << 24 | ((int(R) % 256 )<< 16) | ((int(G) % 256 ) << 8) | (int(B) % 256))
self.repaint()
print "videowin.display_counter = %d" % videowin.display_counter
if __name__ == "__main__":
try:
yuv_file_name = sys.argv[1]
width = int(sys.argv[2])
height = int(sys.argv[3])
f_yuv = open(yuv_file_name, "rb")
videoApp = QApplication(sys.argv)
videowin = VideoWin(width, height, f_yuv)
timer = QTimer()
timer.singleShot(100, videowin.timerSlot)
videowin.show()
videoApp.exec_()
sys.exit(0)
except NameError:
print("Name Error : ", sys.exc_info()[1])
except SystemExit:
print("Closing Window...")
except Exception:
print(sys.exc_info()[1])
I have tried a second approach where I have tried a combination of creating a Signal object which "emits" each decoded RGB image (converted from YUV)as a signal which is caught by the "updateFrame" method in the displaying class which displays the received RGB buffer/frame using QPainter.drawImage(...) method.
YUV-to-RGB decode--->Signal(Image buffer) --->updateFrame ---> QPainter.drawImage(...)
This also displays only the first image alone although the slot which catches the signal (getting the image) shows that it is called as many times as the signal is sent by the YUV->RGB converter/decoder. I have also tried running the YUV->RGB converter and Video display (calling drawImage) in seperate threads, but the result is the same.
Please note that in both the cases, I am writing the RGB pixel values directly into the bit buffer of the QImage object which is part of the VideoWin class in the code shown (NOTE: the code line pix = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=videowin.getImageBuf()) which gets the img.bits() buffer of the QImage class)
Also, for this test I am decoding and displaying only the first 10 frames of the video file.
Versions: Python - 2.7, Qt - 4.8.5 using Pyside
From the docs for array.fromfile():
Read n items (as machine values) from the file object f and append them to the end of the array. [emphasis added]
The example code does not include an offset into the array, and so the first frame is read over and over again. A simple fix would be to clear the array before reading the next frame:
for k in range (0, 100):
del yuv[:]
yuv.fromfile(self.f_yuv, 3*self.width*self.height/2)
And note that, to see a difference, you will need to read at least sixty frames of the test file you linked to, because the first fifty or so are all the same (i.e. a plain green background).
I have got this working based on some modifications (and extensions) to the program suggested in
Displaying a video stream in QLabel with PySide.
I have added a double buffer mechanism between processing and display, used an array to read in YUV file, and finally run the Yuv2Rgb conversion as a separate thread. This works for me - i.e., displays all frames in the file sequentially.
Here is the program for any suggestions and improvements.
Thanks for all your pointers so far!
Please note that this is not running real-time!
#!/usr/bin/python
import sys
import time
from threading import Thread
from PySide.QtCore import *
from PySide.QtGui import *
from PIL import Image
import array
import struct
import numpy as np
class VideoDisplay(QLabel):
def __init__(self):
super(VideoDisplay, self).__init__()
self.disp_counter = 0
def updateFrame(self, image):
self.disp_counter += 1
self.setPixmap(QPixmap.fromImage(image))
class YuvVideoPlayer(QWidget):
video_signal = Signal(QImage)
video_display = None
def __init__(self, f_yuv, width, height):
super(YuvVideoPlayer, self).__init__()
print "Setting up YuvVideoPlayer params"
self.img = {}
self.img[0] = QImage(width, height, QImage.Format_ARGB32)
self.img[1] = QImage(width, height, QImage.Format_ARGB32)
self.video_display = VideoDisplay()
self.video_signal.connect(self.video_display.updateFrame)
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(self.video_display, 0, 0)
self.setLayout(grid)
self.setGeometry(0, 0, width, height)
self.setMinimumSize(width, height)
self.setMaximumSize(width, height)
self.setWindowTitle('Control Center')
print "Creating display thread"
thYuv2Rgb = Thread(target=self.Yuv2Rgb, args=(f_yuv, width, height))
print "Starting display thread"
thYuv2Rgb.start()
self.show()
def Yuv2Rgb(self, f_yuv, width, height):
'''This function gets called by an external thread'''
try:
yuv = array.array('B')
pix = {}
pix[0] = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=self.img[0].bits())
pix[1] = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=self.img[1].bits())
for i in range(0,height):
for j in range(0, width):
pix[0][i, j] = 0
pix[1][i, j] = 0
for k in range (0, 10):
yuv.fromfile(f_yuv, 3*width*height/2)
#y = yuv[0:width*height]
for i in range(0, height):
for j in range(0, width):
Y_val = yuv[(i*width)+j]
U_val = yuv[width*height + ((i/2)*(width/2))+(j/2)]
V_val = yuv[width*height + width*height/4 + ((i/2)*(width/2))+(j/2)]
C = Y_val - 16
D = U_val - 128
E = V_val - 128
R = (( 298 * C + 409 * E + 128) >> 8)
G = (( 298 * C - 100 * D - 208 * E + 128) >> 8)
B = (( 298 * C + 516 * D + 128) >> 8)
if R > 255:
R = 255
if G > 255:
G = 255
if B > 255:
B = 255
pix[k % 2][i, j] = (255 << 24 | ((int(R) % 256 )<< 16) | ((int(G) % 256 ) << 8) | (int(B) % 256))
self.video_signal.emit(self.img[k % 2])
print "Complted pic num %r, disp_counter = %r" % (k, self.video_display.disp_counter)
del yuv[:]
except Exception, e:
print(e)
if __name__ == "__main__":
print "In Main"
yuv_file_name = sys.argv[1]
width = int(sys.argv[2])
height = int(sys.argv[3])
f_yuv = open(yuv_file_name, "rb")
app = QApplication(sys.argv)
print "Creating YuvVideoPlayer object"
ex = YuvVideoPlayer(f_yuv, width, height)
#ex.up_Video_callback(f_yuv, width, height)
app.exec_()
sys.exit(0)

Perspective projection and rotation in python

I've tried searching but none of the other questions seem to be like mine. I'm more or less experimenting with perspective projection and rotation in python, and have run into a snag. I'm sure my projection equations are accurate, as well as my rotation equations; however, when I run it, the rotation starts normal, but begins to swirl inwards until the vector is in the same position as the Z axis (the axis I am rotating over).
''' Imports '''
from tkinter import Tk, Canvas, TclError
from threading import Thread
from math import cos, sin, radians, ceil
from time import sleep
''' Points class '''
class pPoint:
def __init__(self, fPoint, wWC, wHC):
self.X = 0
self.Y = 0
self.Z = 0
self.xP = 0
self.yP = 0
self.fPoint = fPoint
self.wWC = wWC
self.wHC = wHC
def pProject(self):
self.xP = (self.fPoint * (self.X + self.wWC)) / (self.fPoint + self.Z)
self.yP = (self.fPoint * (self.Y + self.wHC)) / (self.fPoint + self.Z)
''' Main class '''
class Main:
def __init__(self):
''' Declarations '''
self.wWidth = 640
self.wHeight = 480
self.fPoint = 256
''' Generated declarations '''
self.wWC = self.wWidth / 2
self.wHC = self.wHeight / 2
''' Misc declarations '''
self.gWin = Tk()
self.vPoint = pPoint(self.fPoint, self.wWC, self.wHC)
self.vPoint.X = 50
self.vPoint.Y = 60
self.vPoint.Z = -25
self.vPoint.pProject()
self.ang = 0
def initWindow(self):
self.gWin.minsize(self.wWidth, self.wHeight)
self.gWin.maxsize(self.wWidth, self.wHeight)
''' Create canvas '''
self.gCan = Canvas(self.gWin, width = self.wWidth, height = self.wHeight, background = "black")
self.gCan.pack()
def setAxis(self):
''' Create axis points '''
self.pXax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pXbx = pPoint(self.fPoint, self.wWC, self.wHC)
self.pYax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pYbx = pPoint(self.fPoint, self.wWC, self.wHC)
self.pZax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pZbx = pPoint(self.fPoint, self.wWC, self.wHC)
''' Set axis points '''
self.pXax.X = -(self.wWC)
self.pXax.Y = 0
self.pXax.Z = 1
self.pXbx.X = self.wWC
self.pXbx.Y = 0
self.pXbx.Z = 1
self.pYax.X = 0
self.pYax.Y = -(self.wHC)
self.pYax.Z = 1
self.pYbx.X = 0
self.pYbx.Y = self.wHC
self.pYbx.Z = 1
self.pZax.X = 0
self.pZax.Y = 0
self.pZax.Z = -(self.fPoint) / 2
self.pZbx.X = 0
self.pZbx.Y = 0
self.pZbx.Z = (self.fPoint * self.wWC) - self.fPoint
def projAxis(self):
''' Project the axis '''
self.pXax.pProject()
self.pXbx.pProject()
self.pYax.pProject()
self.pYbx.pProject()
self.pZax.pProject()
self.pZbx.pProject()
def drawAxis(self):
''' Draw the axis '''
self.gCan.create_line(self.pXax.xP, self.pXax.yP, self.pXbx.xP, self.pXbx.yP, fill = "white")
self.gCan.create_line(self.pYax.xP, self.pYax.yP, self.pYbx.xP, self.pYbx.yP, fill = "white")
self.gCan.create_line(self.pZax.xP, self.pZax.yP, self.pZbx.xP, self.pZbx.yP, fill = "white")
def prePaint(self):
self.vA = self.gCan.create_line(self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP, fill = "red")
def paintCanvas(self):
try:
while True:
self.ang += 1
if self.ang >= 361:
self.ang = 0
self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))) - (self.vPoint.Y * sin(radians(self.ang)))
self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))) + (self.vPoint.Y * cos(radians(self.ang)))
self.vPoint.pProject()
self.gCan.coords(self.vA, self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP)
self.gWin.update_idletasks()
self.gWin.update()
sleep(0.1)
except TclError:
pass
mMain = Main()
mMain.initWindow()
mMain.setAxis()
mMain.projAxis()
mMain.drawAxis()
mMain.prePaint()
mMain.paintCanvas()
Thank you for any input :)
EDIT: Sorry, I just realized I forgot to put my question. I just want to know why it is gravitating inward, and not just rotating "normally"?
This section is wrong:
self.ang += 1
if self.ang >= 361:
self.ang = 0
self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))
- self.vPoint.Y * sin(radians(self.ang)))
self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))
+ self.vPoint.Y * cos(radians(self.ang)))
self.vPoint.pProject()
For two reasons:
self.ang will take integers in the open range [0 - 360], which means the angle 360 (== 0) is repeated.
In each iteration, you rotate the point from the previous iteration by the angle. As a result, your first frame is at 1 degree, your second at 1+2 = 3, the third at 1 + 2 + 3... You should either be:
rotating the point from the previous iteration by a constant angle each time (1°). This suffers from the problem mentioned in my comment
rotating the initial point by the current angle of rotation each time
Not actualy related to your problem, but I strongly suggest you to use Numpy to perform geometric transformations, specially if it involves 3D points.
Below, I post a sample snippet, I hope it helps:
import numpy
from math import radians, cos, sin
## suppose you have a Nx3 cloudpoint (it might even be a single row of x,y,z coordinates)
cloudpoint = give_me_a_cloudpoint()
## this will be a rotation around Y azis:
yrot = radians(some_angle_in_degrees)
## let's create a rotation matrix using a numpy array
yrotmatrix = numpy.array([[cos(yrot), 0, -sin(yrot)],
[0, 1, 0],
[sin(yrot), 0, cos(yrot)]], dtype=float)
## apply the rotation via dot multiplication
rotatedcloud = numpy.dot(yrotmatrix, pointcloud.T).T # .T means transposition

Categories