glumpy icon indicating copy to clipboard operation
glumpy copied to clipboard

app.Window doesn't create from new multiprocessing.Process

Open jelicicm opened this issue 4 years ago • 3 comments

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.

jelicicm avatar Aug 06 '21 11:08 jelicicm

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.

rougier avatar Aug 19 '21 08:08 rougier

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.

jelicicm avatar Aug 20 '21 14:08 jelicicm

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.

rougier avatar Aug 30 '21 08:08 rougier