app.Window doesn't create from new multiprocessing.Process
I've noticed a weird behavior when trying to run Glumpy visualization from a new multiprocessing.Process process. When I run it in the application's default process, this code works good, but otherwise two windows are not drawn at all. System is Ubuntu 20.04. On Windows 10, all functions good!
#!/usr/bin/env python3
# -----------------------------------------------------------------------------
# Copyright (c) 2009-2016 Nicolas P. Rougier. All rights reserved.
# Distributed under the (new) BSD License.
# -----------------------------------------------------------------------------
import pickle
import os
import numpy as np
from glumpy import app, gl, gloo, glm
import time
from multiprocessing import Process
from myapp.visualization.gloo_text import Console
vertex = """
#version 120
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform float linewidth;
uniform float antialias;
attribute vec4 fg_color;
attribute vec4 bg_color;
attribute float radius;
attribute vec3 position;
varying float v_pointsize;
varying float v_radius;
varying vec4 v_fg_color;
varying vec4 v_bg_color;
void main (void)
{
v_radius = radius;
v_fg_color = fg_color;
v_bg_color = bg_color;
gl_Position = projection * view * model * vec4(position,1.0);
gl_PointSize = 2 * (v_radius + linewidth + 1.5*antialias);
}
"""
fragment = """
#version 120
uniform float linewidth;
uniform float antialias;
varying float v_radius;
varying vec4 v_fg_color;
varying vec4 v_bg_color;
float marker(vec2 P, float size)
{
const float SQRT_2 = 1.4142135623730951;
float x = SQRT_2/2 * (P.x - P.y);
float y = SQRT_2/2 * (P.x + P.y);
float r1 = max(abs(x)- size/2, abs(y)- size/10);
float r2 = max(abs(y)- size/2, abs(x)- size/10);
float r3 = max(abs(P.x)- size/2, abs(P.y)- size/10);
float r4 = max(abs(P.y)- size/2, abs(P.x)- size/10);
return min( min(r1,r2), min(r3,r4));
}
void main()
{
float r = (v_radius + linewidth + 1.5*antialias);
float t = linewidth/2.0 - antialias;
float signed_distance = length(gl_PointCoord.xy - vec2(0.5,0.5)) * 2 * r - v_radius;
// float signed_distance = marker((gl_PointCoord.xy - vec2(0.5,0.5))*r*2, 2*v_radius);
float border_distance = abs(signed_distance) - t;
float alpha = border_distance/antialias;
alpha = exp(-alpha*alpha);
// Inside shape
if( signed_distance < 0 ) {
// Fully within linestroke
if( border_distance < 0 ) {
gl_FragColor = v_fg_color;
} else {
gl_FragColor = mix(v_bg_color, v_fg_color, alpha);
}
// Outside shape
} else {
// Fully within linestroke
if( border_distance < 0 ) {
gl_FragColor = v_fg_color;
} else if( abs(signed_distance) < (linewidth/2.0 + antialias) ) {
gl_FragColor = vec4(v_fg_color.rgb, v_fg_color.a * alpha);
} else {
discard;
}
}
}
"""
#gl.glEnable(gl.GL_DEPTH_TEST)
num_of_frames = None
frames_dir = None
gt_labels_dir = None
thx_labels_dir = None
def get_file_count(directory_path):
cnt = [f for f in os.listdir(directory_path) if f.endswith('.pkl')]
return len(cnt)
def set_dir_paths(work_dir_path):
global frames_dir, gt_labels_dir, thx_labels_dir
# frames_dir = '/work/visualization/full_seq_0/frames'
frames_dir = os.path.join(work_dir_path, 'frames')
# gt_labels_dir = '/work/visualization/full_seq_0/ground_truth'
gt_labels_dir = os.path.join(work_dir_path, 'ground_truth')
# thx_labels_dir = '/work/visualization/full_seq_0/thx_output'
thx_labels_dir = os.path.join(work_dir_path, 'thx_output')
'''
Constants
'''
ns_per_us = 1000.
us_per_ms = 1000.
ms_per_s = 1000.
num_of_points_per_frame = 45056
custom_colors = [
(0, 0, 0), #'unlabeled'
(0, 0, 1), #'car'
(1, 0.8, 0.4), #'bicycle'
(0.5, 0.2, 0.1), #'motorcycle'
(0.75, 0.1, 0.2), #'truck'
(0.8, 0.2, 0.1), #'other-vehicle'
(0.1, 0.1, 1), #'person'
(1, 0.2, 1), #'bicyclist'
(0.5, 0.2, 0.2), #'motorcyclist'
(1, 0.2, 1), #'road'
(1, 0.5, 1), #'parking'
(0.8, 0.6, 0.1), #'sidewalk'
(0.2, 0.2, 0.6), #'other-ground'
(0.2, 0.75, 1), #'building'
(0.033, 0.4, 1), #'fence'
(0, 0.5, 0), #'vegetation'=40
(0.75, 0.2, 0.2), #'truck'
(0.2, 1, 0.5), #'terrain'
(0.5, 1, 1), #'pole'
(0, 0, 1), #'traffic-sign'
]
'''
'''
##############################################################################################################
view = np.eye(4, dtype=np.float32)
glm.translate(view, 0, 0, -5)
##############################################################################################################
def create_gt():
program_gt = gloo.Program(vertex, fragment, count=num_of_points_per_frame)
program_gt['radius'] = np.random.uniform(5, 5, num_of_points_per_frame)
program_gt['fg_color'] = 0, 0, 0, 1
program_gt['linewidth'] = -2
program_gt['antialias'] = 1.0
program_gt['model'] = np.eye(4, dtype=np.float32)
program_gt['view'] = view
window_gt = app.Window(width=1920 // 2, height=1080, color=(1, 1, 1, 1))
window_gt.set_position(-1, 0)
window_gt.set_title('GT Labels')
console_gt = Console(2, 21, put_x=1, put_y=1, scale=3)
return program_gt, window_gt, console_gt
##############################################################################################################
def create_thx():
program_thx = gloo.Program(vertex, fragment, count=num_of_points_per_frame)
program_thx['radius'] = np.random.uniform(5, 5, num_of_points_per_frame)
program_thx['fg_color'] = 0, 0, 0, 1
program_thx['linewidth'] = -2
program_thx['antialias'] = 1.0
program_thx['model'] = np.eye(4, dtype=np.float32)
program_thx['view'] = view
window_thx = app.Window(width=1920 // 2, height=1080, color=(1, 1, 1, 1))
window_thx.set_position(1920 // 2, 0)
window_thx.set_title('ThX Labels')
console_thx = Console(2, 19, put_x=1, put_y=1, scale=3)
return program_thx, window_thx, console_thx
##############################################################################################################
import queue
frame_counter = 0
default_labels = np.zeros((45056, ), dtype=np.uint8) # *255
old_labels = default_labels
subcloud_labels = default_labels
def start_animation(visualization_queue, work_dir, counter):
global num_of_frames
print(counter)
set_dir_paths(work_dir)
num_of_frames = get_file_count(frames_dir)
print(f'num of frames = {num_of_frames}')
program_gt, window_gt, console_gt = create_gt()
program_thx, window_thx, console_thx = create_thx()
quantization_factor = (2**7)
##############################################################################################################
@window_thx.event
def on_draw(dt):
global frame_counter, subcloud_labels, old_labels
global frame_fn, gt_label_fn, thx_label_fn
subcloud_labels = old_labels
if frame_counter == 0:
frame_fn = f'{frames_dir}/{frame_counter:06}.pkl'
gt_label_fn = f'{gt_labels_dir}/{frame_counter:06}.pkl'
thx_label_fn = f'{thx_labels_dir}/{frame_counter:06}.pkl'
try:
new_labels = visualization_queue.get_nowait()
counter.increment()
subcloud_labels = pickle.loads(new_labels)
subcloud_labels = np.frombuffer(subcloud_labels, dtype=np.uint8).copy()
old_labels = subcloud_labels
frame_fn = f'{frames_dir}/{frame_counter:06}.pkl'
gt_label_fn = f'{gt_labels_dir}/{frame_counter:06}.pkl'
thx_label_fn = f'{thx_labels_dir}/{frame_counter:06}.pkl'
if frame_counter < (num_of_frames - 1):
if (frame_counter % 100 == 0) or (frame_counter > num_of_frames - 150):
print(f'frame_counter = {frame_counter}')
frame_counter += 1
else:
frame_counter = 0
print(f'last frame!!!!')
except queue.Empty:
pass
with open(frame_fn, 'rb') as f:
subcloud = pickle.load(f).astype(np.float64) / quantization_factor
# with open(thx_label_fn, 'rb') as f:
# subcloud_labels = pickle.load(f)
# subcloud_labels = np.frombuffer(subcloud_labels, dtype=np.uint8).copy()
# subcloud_labels = np.frombuffer(subcloud_labels, dtype=np.uint8).copy()
ins_colors = custom_colors
sem_ins_labels = np.unique(subcloud_labels)
Y_colors = np.zeros((subcloud_labels.shape[0], 3))
for id, semins in enumerate(sem_ins_labels):
valid_ind = np.argwhere(subcloud_labels == semins)[:, 0]
tp = ins_colors[semins]
Y_colors[valid_ind] = tp
ones = np.ones((len(Y_colors), 1))
Y_colors = np.concatenate((Y_colors, ones), axis=-1)
gt_label = load_pkl_file(gt_label_fn)
thx_label = subcloud_labels
accuracy = compare_point_clouds(gt_label, thx_label)
console_thx.clear()
window_thx.clear()
program_thx.draw(gl.GL_POINTS)
program_thx['position'] = subcloud
colors = Y_colors
program_thx['bg_color'] = colors
console_thx.write(f'Accuracy: {accuracy:.2f}%')
console_thx.draw()
@window_thx.event
def on_resize(width, height):
program_thx['projection'] = glm.perspective(110.0, width / float(height), 1.0, 1000.0)
##############################################################################################################
@window_gt.event
def on_draw(dt):
global frame_counter, t_start
global frame_fn, gt_label_fn, thx_label_fn
if frame_counter == 0:
frame_fn = f'{frames_dir}/{frame_counter:06}.pkl'
gt_label_fn = f'{gt_labels_dir}/{frame_counter:06}.pkl'
thx_label_fn = f'{thx_labels_dir}/{frame_counter:06}.pkl'
with open(frame_fn, 'rb') as f:
subcloud = pickle.load(f).astype(np.float64) / quantization_factor
with open(gt_label_fn, 'rb') as f:
subcloud_labels = pickle.load(f)
# subcloud_labels = np.frombuffer(subcloud_labels, dtype=np.uint8).copy()
ins_colors = custom_colors
sem_ins_labels = np.unique(subcloud_labels)
Y_colors = np.zeros((subcloud_labels.shape[0], 3))
for id, semins in enumerate(sem_ins_labels):
valid_ind = np.argwhere(subcloud_labels == semins)[:, 0]
tp = ins_colors[semins]
Y_colors[valid_ind] = tp
ones = np.ones((len(Y_colors), 1))
Y_colors = np.concatenate((Y_colors, ones), axis=-1)
console_gt.clear()
window_gt.clear()
program_gt.draw(gl.GL_POINTS)
program_gt['position'] = subcloud
colors = Y_colors
program_gt['bg_color'] = colors
console_gt.write(f'Ground Truth: 100%')
console_gt.draw()
@window_gt.event
def on_resize(width, height):
program_gt['projection'] = glm.perspective(110.0, width / float(height), 1.0, 1000.0)
##############################################################################################################
window_thx.attach(console_thx)
window_gt.attach(console_gt)
app.run(framerate=20)
def create_animation_process(visualization_queue, work_dir, counter):
animation_proc = Process(target=start_animation, args=(visualization_queue, work_dir, counter))
return animation_proc
When I invoke start_animation(...) all looks good, but when I call new_proc = create_animation_process(...); new_proc.start(), windows are not drawn, even though I get the usual Glumpy messages.
Can you check if the example https://github.com/glumpy/glumpy/blob/master/examples/app-two-windows.py and https://github.com/glumpy/glumpy/blob/master/examples/app-two-programs.py works properly ?
Concerning the multiprocess, I'm not sure this is compatible with Python OpenGL actually.
Hello, yes this example you posted the link to works as expected - black background square is drawn with blue&red squares. Regarding your OpenGL comment - I do actually try to run Glumpy from single process, its just that it's not the process from which the application started. I realize that the problem might occur if we're talking about 'main' and 'child' threads, but since these are all OS level processes, I don't see how anything can be "main" or not.
I'm not too sure, but I think the GL Context must be created/owned by a single process and this might be problematic if the parent creates the context and the child try to write to it. Maybe a first move would be to check with PyOpenGL or PyGame if there are any example with multiprocesses.