class Window:
    def __init__(self):
        if not glfw.init():
            raise RuntimeError("Unable to initialize glfw.")

        w_window, h_window = 800, 600

        self.window = glfw.create_window(w_window, h_window,
                                         "Voxel Visualizer", None, None)

        if not self.window:
            glfw.terminate()
            raise RuntimeError("Unable to create window.")

        monitor = glfw.get_primary_monitor()
        mode = glfw.get_video_mode(monitor)
        w_mon, h_mon = mode.size
        # center window.
        glfw.set_window_pos(self.window, int(0.5 * w_mon - 0.5 * w_window),
                            int(0.5 * h_mon - 0.5 * h_window))

        # Make the window's context current
        glfw.make_context_current(self.window)

        glfw.set_framebuffer_size_callback(self.window, self.on_resize)
        # add mouse handlers.
        glfw.set_input_mode(self.window, glfw.STICKY_MOUSE_BUTTONS, glfw.TRUE)
        glfw.set_mouse_button_callback(self.window, self.on_mouse_btn)
        glfw.set_cursor_pos_callback(self.window, self.on_mouse_move)
        glfw.set_window_size_callback(self.window, self.on_resize)

        glfw.set_key_callback(self.window, self.keyboard_callback)
        glfw.set_char_callback(self.window, self.char_callback)
        glfw.set_scroll_callback(self.window, self.scroll_callback)

        self.voxel_dims = glow.ivec3(256, 256, 32)

        # read config file.
        CFG = yaml.safe_load(open("config/semantic-kitti.yaml", 'r'))
        color_dict = CFG["color_map"]
        self.label_colors = glow.GlTextureRectangle(1024,
                                                    1,
                                                    internalFormat=GL_RGB,
                                                    format=GL_RGB)

        cols = np.zeros((1024 * 3), dtype=np.uint8)
        for label_id, color in color_dict.items():
            cols[3 * label_id + 0] = color[2]
            cols[3 * label_id + 1] = color[1]
            cols[3 * label_id + 2] = color[0]

        self.label_colors.assign(cols)

        self.initializeGL()

        # initialize imgui
        imgui.create_context()
        self.impl = GlfwRenderer(self.window, attach_callbacks=False)

        self.on_resize(self.window, w_window, h_window)

        self.data = []
        self.isDrag = False
        self.buttonPressed = None
        self.cam = Camera()
        self.cam.lookAt(25.0, 25.0, 25.0, 0.0, 0.0, 0.0)

        self.currentTimestep = 0
        self.sliderValue = 0
        self.showLabels = True

    def initializeGL(self):
        """ initialize GL related stuff. """

        self.num_instances = np.prod(self.voxel_dims)

        # see https://stackoverflow.com/questions/28375338/cube-using-single-gl-triangle-strip, but the normals are a problem.
        # verts = np.array([
        #     -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1,
        #     -1, 1, -1, -1, -1, 1, -1, 1, 1, -1
        # ],
        #                  dtype=np.float32)

        # yapf: disable

        p1 = [1, 0, 0]
        p2 = [0, 0, 0]
        p3 = [1, 1, 0]
        p4 = [0, 1, 0]
        p5 = [1, 0, 1]
        p6 = [0, 0, 1]
        p7 = [0, 1, 1]
        p8 = [1, 1, 1]

        verts = np.array([
            # first face
            p4, p3, p7, p3, p7, p8,
            # second face
            p7, p8, p5, p7, p6, p5,
            # third face
            p8, p5, p3, p5, p3, p1,
            # fourth face
            p3, p1, p4, p1, p4, p2,
            # fifth face
            p4, p2, p7, p2, p7, p6,
            # sixth face
            p6, p5, p2, p5, p2, p1
        ], dtype=np.float32).reshape(-1)

        normals = np.array([[0, 1, 0] * 6,
                            [0, 0, 1] * 6,
                            [1, 0, 0] * 6,
                            [0, 0, -1] * 6,
                            [-1, 0, 0] * 6,
                            [0, -1, 0] * 6
                            ], dtype=np.float32).reshape(-1)

        # yapf: enable
        glow.WARN_INVALID_UNIFORMS = True

        self.labels = np.array([], dtype=np.float32)

        self.cube_verts = glow.GlBuffer()
        self.cube_verts.assign(verts)
        self.cube_normals = glow.GlBuffer()
        self.cube_normals.assign(normals)
        self.label_vbo = glow.GlBuffer()

        glPointSize(5.0)

        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)

        SIZEOF_FLOAT = 4

        self.cube_verts.bind()
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * SIZEOF_FLOAT,
                              GLvoidp(0))
        glEnableVertexAttribArray(0)
        self.cube_verts.release()

        self.cube_normals.bind()
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * SIZEOF_FLOAT,
                              GLvoidp(0))
        glEnableVertexAttribArray(1)
        self.cube_normals.release()

        glEnableVertexAttribArray(2)
        self.label_vbo.bind()
        # Note: GL_UNSINGED_INT did not work as expected! I could not figure out what was wrong there!
        glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, SIZEOF_FLOAT,
                              GLvoidp(0))
        self.label_vbo.release()
        glVertexAttribDivisor(2, 1)

        glBindVertexArray(0)

        self.program = glow.GlProgram()
        self.program.attach(
            glow.GlShader.fromFile(GL_VERTEX_SHADER,
                                   "auxiliary/shaders/draw_voxels.vert"))
        self.program.attach(
            glow.GlShader.fromFile(GL_FRAGMENT_SHADER,
                                   "auxiliary/shaders/draw_voxels.frag"))
        self.program.link()

        self.prgDrawPose = glow.GlProgram()
        self.prgDrawPose.attach(
            glow.GlShader.fromFile(GL_VERTEX_SHADER,
                                   "auxiliary/shaders/empty.vert"))
        self.prgDrawPose.attach(
            glow.GlShader.fromFile(GL_GEOMETRY_SHADER,
                                   "auxiliary/shaders/draw_pose.geom"))
        self.prgDrawPose.attach(
            glow.GlShader.fromFile(GL_FRAGMENT_SHADER,
                                   "auxiliary/shaders/passthrough.frag"))
        self.prgDrawPose.link()

        self.prgTestUniform = glow.GlProgram()
        self.prgTestUniform.attach(
            glow.GlShader.fromFile(GL_VERTEX_SHADER,
                                   "auxiliary/shaders/check_uniforms.vert"))
        self.prgTestUniform.attach(
            glow.GlShader.fromFile(GL_FRAGMENT_SHADER,
                                   "auxiliary/shaders/passthrough.frag"))
        self.prgTestUniform.link()

        # general parameters
        glClearColor(1.0, 1.0, 1.0, 1.0)
        glEnable(GL_DEPTH_TEST)
        glDepthFunc(GL_LEQUAL)
        glEnable(GL_LINE_SMOOTH)

        # x = forward, y = left, z = up to x = right, y = up, z = backward
        self.conversion_ = np.array(
            [0, -1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0, 0, 0, 1],
            dtype=np.float32).reshape(4, 4)

        self.program.bind()

        self.program["voxel_size"] = 0.5
        self.program["voxel_dims"] = self.voxel_dims
        self.program["label_colors"] = 0

        self.program.release()

        self.vao_no_points = glGenVertexArrays(1)

    def open_directory(self, directory):
        """ open given sequences directory and get filenames of relevant files. """
        self.subdirs = [
            subdir for subdir in ["voxels", "predictions"]
            if os.path.exists(os.path.join(directory, subdir))
        ]

        if len(self.subdirs) == 0:
            raise RuntimeError("Neither 'voxels' nor 'predictions' found in " +
                               directory)

        self.availableData = {}
        self.data = {}

        for subdir in self.subdirs:
            self.availableData[subdir] = []
            self.data[subdir] = {}
            complete_path = os.path.join(directory, subdir)
            files = os.listdir(complete_path)

            data = sorted([
                os.path.join(complete_path, f) for f in files
                if f.endswith(".bin")
            ])
            if len(data) > 0:
                self.availableData[subdir].append("input")
                self.data[subdir]["input"] = data
                self.num_scans = len(data)

            data = sorted([
                os.path.join(complete_path, f) for f in files
                if f.endswith(".label")
            ])
            if len(data) > 0:
                self.availableData[subdir].append("labels")
                self.data[subdir]["labels"] = data
                self.num_scans = len(data)

            data = sorted([
                os.path.join(complete_path, f) for f in files
                if f.endswith(".invalid")
            ])
            if len(data) > 0:
                self.availableData[subdir].append("invalid")
                self.data[subdir]["invalid"] = data
                self.num_scans = len(data)

            data = sorted([
                os.path.join(complete_path, f) for f in files
                if f.endswith(".occluded")
            ])
            if len(data) > 0:
                self.availableData[subdir].append("occluded")
                self.data[subdir]["occluded"] = data
                self.num_scans = len(data)

        self.current_subdir = 0
        self.current_data = self.availableData[self.subdirs[
            self.current_subdir]][0]

        self.currentTimestep = 0
        self.sliderValue = 0

        self.lastChange = None
        self.lastUpdate = time.time()
        self.button_backward_hold = False
        self.button_forward_hold = False

        # todo: modify based on available stuff.
        self.showLabels = (self.current_data == "labels")
        self.showInput = (self.current_data == "input")
        self.showInvalid = (self.current_data == "invalid")
        self.showOccluded = (self.current_data == "occludded")

    def setCurrentBufferData(self, data_name, t):
        # update buffer content with given data identified by data_name.
        subdir = self.subdirs[self.current_subdir]

        if len(self.data[subdir][data_name]) < t: return False

        # Note: uint with np.uint32 did not work as expected! (with instances and uint32 this causes problems!)
        if data_name == "labels":
            buffer_data = np.fromfile(self.data[subdir][data_name][t],
                                      dtype=np.uint16).astype(np.float32)
        else:
            buffer_data = unpack(
                np.fromfile(self.data[subdir][data_name][t],
                            dtype=np.uint8)).astype(np.float32)

        self.label_vbo.assign(buffer_data)

        return True

    def on_resize(self, window, w, h):
        # set projection matrix
        fov = math.radians(45.0)
        aspect = w / h

        self.projection_ = glPerspective(fov, aspect, 0.1, 2000.0)

        self.impl.resize_callback(window, w, h)

    def on_mouse_btn(self, window, button, action, mods):
        x, y = glfw.get_cursor_pos(self.window)

        imgui.get_io().mouse_pos = (x, y)
        if imgui.get_io().want_capture_mouse:

            return

        if action == glfw.PRESS:
            self.buttonPressed = button
            self.isDrag = True
            self.cam.mousePressed(x, y, self.buttonPressed, None)
        else:
            self.buttonPressed = None
            self.isDrag = False
            self.cam.mouseReleased(x, y, self.buttonPressed, None)

    def on_mouse_move(self, window, x, y):
        if self.isDrag:
            self.cam.mouseMoved(x, y, self.buttonPressed, None)

    def keyboard_callback(self, window, key, scancode, action, mods):
        self.impl.keyboard_callback(window, key, scancode, action, mods)

        if not imgui.get_io().want_capture_keyboard:
            if key == glfw.KEY_B or key == glfw.KEY_LEFT:
                self.currentTimestep = self.sliderValue = max(
                    0, self.currentTimestep - 1)

            if key == glfw.KEY_N or key == glfw.KEY_RIGHT:
                self.currentTimestep = self.sliderValue = min(
                    self.num_scans - 1, self.currentTimestep + 1)

            if key == glfw.KEY_Q or key == glfw.KEY_ESCAPE:
                exit(0)

    def char_callback(self, window, char):
        self.impl.char_callback(window, char)

    def scroll_callback(self, window, x_offset, y_offset):
        self.impl.scroll_callback(window, x_offset, y_offset)

    def run(self):
        # Loop until the user closes the window
        while not glfw.window_should_close(self.window):
            # Poll for and process events
            glfw.poll_events()

            # build gui.
            self.impl.process_inputs()

            w, h = glfw.get_window_size(self.window)
            glViewport(0, 0, w, h)

            imgui.new_frame()

            timeline_height = 35
            imgui.push_style_var(imgui.STYLE_WINDOW_ROUNDING, 0)
            imgui.push_style_var(imgui.STYLE_FRAME_ROUNDING, 0)

            imgui.set_next_window_position(0, h - timeline_height - 10)
            imgui.set_next_window_size(w, timeline_height)

            imgui.begin(
                "Timeline", False, imgui.WINDOW_NO_TITLE_BAR
                | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_SCROLLBAR)
            imgui.columns(1)
            imgui.same_line(0, 0)
            imgui.push_item_width(-50)
            changed, value = imgui.slider_int("", self.sliderValue, 0,
                                              self.num_scans - 1)
            if changed: self.sliderValue = value
            if self.sliderValue != self.currentTimestep:
                self.currentTimestep = self.sliderValue

            imgui.push_style_var(imgui.STYLE_FRAME_ROUNDING, 3)

            play_delay = 1
            refresh_rate = 0.05

            current_time = time.time()

            imgui.same_line(spacing=5)
            changed = imgui.button("<", 20)
            if self.currentTimestep > 0:
                # just a click
                if changed:
                    self.currentTimestep = self.sliderValue = self.currentTimestep - 1
                    self.lastUpdate = current_time

                # button pressed.
                if imgui.is_item_active() and not self.button_backward_hold:
                    self.hold_start = current_time
                    self.button_backward_hold = True

                if not imgui.is_item_active() and self.button_backward_hold:
                    self.button_backward_hold = False

                # start playback when button pressed long enough
                if self.button_backward_hold and (
                    (current_time - self.hold_start) > play_delay):
                    if (current_time - self.lastUpdate) > refresh_rate:
                        self.currentTimestep = self.sliderValue = self.currentTimestep - 1
                        self.lastUpdate = current_time

            imgui.same_line(spacing=2)
            changed = imgui.button(">", 20)

            if self.currentTimestep < self.num_scans - 1:
                # just a click
                if changed:
                    self.currentTimestep = self.sliderValue = self.currentTimestep + 1
                    self.lastUpdate = current_time

                # button pressed.
                if imgui.is_item_active() and not self.button_forward_hold:
                    self.hold_start = current_time
                    self.button_forward_hold = True

                if not imgui.is_item_active() and self.button_forward_hold:
                    self.button_forward_hold = False

                # start playback when button pressed long enough
                if self.button_forward_hold and (
                    (current_time - self.hold_start) > play_delay):
                    if (current_time - self.lastUpdate) > refresh_rate:
                        self.currentTimestep = self.sliderValue = self.currentTimestep + 1
                        self.lastUpdate = current_time

            imgui.pop_style_var(3)
            imgui.end()

            imgui.set_next_window_position(20, 20, imgui.FIRST_USE_EVER)
            imgui.set_next_window_size(200, 150, imgui.FIRST_USE_EVER)
            imgui.begin("Show Data")

            if len(self.subdirs) > 1:
                for i, subdir in enumerate(self.subdirs):
                    changed, value = imgui.checkbox(subdir,
                                                    self.current_subdir == i)
                    if i < len(self.subdirs) - 1: imgui.same_line()
                    if changed and value: self.current_subdir = i

            subdir = self.subdirs[self.current_subdir]

            data_available = "input" in self.availableData[subdir]
            if data_available:
                imgui.push_style_var(imgui.STYLE_ALPHA, 1.0)
            else:
                imgui.push_style_var(imgui.STYLE_ALPHA, 0.3)

            changed, value = imgui.checkbox("input", self.showInput)
            if changed and value and data_available:
                self.showInput = True
                self.showLabels = False

            imgui.pop_style_var()

            data_available = "labels" in self.availableData[subdir]
            if data_available:
                imgui.push_style_var(imgui.STYLE_ALPHA, 1.0)
            else:
                imgui.push_style_var(imgui.STYLE_ALPHA, 0.3)

            changed, value = imgui.checkbox("labels", self.showLabels)
            if changed and value and data_available:
                self.showInput = False
                self.showLabels = True

            imgui.pop_style_var()

            data_available = "invalid" in self.availableData[subdir]
            if data_available:
                imgui.push_style_var(imgui.STYLE_ALPHA, 1.0)
            else:
                imgui.push_style_var(imgui.STYLE_ALPHA, 0.3)

            changed, value = imgui.checkbox("invalid", self.showInvalid)
            if changed and data_available: self.showInvalid = value

            imgui.pop_style_var()

            data_available = "occluded" in self.availableData[subdir]
            if data_available:
                imgui.push_style_var(imgui.STYLE_ALPHA, 1.0)
            else:
                imgui.push_style_var(imgui.STYLE_ALPHA, 0.3)

            changed, value = imgui.checkbox("occluded", self.showOccluded)
            if changed and data_available: self.showOccluded = value

            imgui.pop_style_var()

            imgui.end()

            # imgui.show_demo_window()

            showData = []
            if self.showInput: showData.append("input")
            if self.showOccluded: showData.append("occluded")
            if self.showInvalid: showData.append("invalid")

            mvp = self.projection_ @ self.cam.matrix @ self.conversion_

            glClearColor(1.0, 1.0, 1.0, 1.0)

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glBindVertexArray(self.vao)

            self.program.bind()
            self.program["mvp"] = mvp.transpose()
            self.program["view_mat"] = (
                self.cam.matrix @ self.conversion_).transpose()
            self.program["lightPos"] = glow.vec3(10, 10, 10)
            self.program["voxel_scale"] = 0.8
            self.program["voxel_alpha"] = 1.0
            self.program["use_label_colors"] = True

            self.label_colors.bind(0)

            if self.showLabels:
                self.setCurrentBufferData("labels", self.currentTimestep)
                glDrawArraysInstanced(GL_TRIANGLES, 0, 36, self.num_instances)

            self.program["use_label_colors"] = False
            self.program["voxel_color"] = glow.vec3(0.3, 0.3, 0.3)

            self.program["voxel_alpha"] = 0.5

            for data_name in showData:
                self.program["voxel_scale"] = 0.5
                if data_name == "input": self.program["voxel_scale"] = 0.8

                self.setCurrentBufferData(data_name, self.currentTimestep)
                glDrawArraysInstanced(GL_TRIANGLES, 0, 36, self.num_instances)

            self.program.release()
            self.label_colors.release(0)

            glBindVertexArray(self.vao_no_points)

            self.prgDrawPose.bind()
            self.prgDrawPose["mvp"] = mvp.transpose()
            self.prgDrawPose["pose"] = np.identity(4, dtype=np.float32)
            self.prgDrawPose["size"] = 1.0

            glDrawArrays(GL_POINTS, 0, 1)
            self.prgDrawPose.release()

            glBindVertexArray(0)

            # draw gui ontop.
            imgui.render()
            self.impl.render(imgui.get_draw_data())

            # Swap front and back buffers
            glfw.swap_buffers(self.window)
Beispiel #2
0
class App:
    def __init__(self, width=640, height=480, title="Hello world"):
        imgui.create_context()

        if not glfw.init():
            return

        self.window = glfw.create_window(width, height, title, None, None)
        if not self.window:
            glfw.terminate()
            return

        glfw.make_context_current(self.window)
        self.ctx = moderngl.create_context(require=460)

        self.impl = ImguiRenderer(self.window, attach_callbacks=False)

        glfw.set_key_callback(self.window, self._on_key)
        glfw.set_cursor_pos_callback(self.window, self._on_mouse_move)
        glfw.set_mouse_button_callback(self.window, self._on_mouse_button)
        glfw.set_window_size_callback(self.window, self._on_resize)
        glfw.set_char_callback(self.window, self._on_char)
        glfw.set_scroll_callback(self.window, self._on_scroll)

        self.init()

    def main_loop(self):
        previous_time = glfw.get_time()

        # Loop until the user closes the window
        while not glfw.window_should_close(self.window):
            glfw.poll_events()
            self.impl.process_inputs()

            current_time = glfw.get_time()
            delta_time = current_time - previous_time
            previous_time = current_time
            self.update(current_time, delta_time)
            self.render()

            imgui.new_frame()
            self.ui()
            imgui.render()
            self.impl.render(imgui.get_draw_data())

            glfw.swap_buffers(self.window)

        self.impl.shutdown()
        glfw.terminate()

    def should_close(self):
        glfw.set_window_should_close(self.window, True)

    def mouse_pos(self):
        return glfw.get_cursor_pos(self.window)

    def size(self):
        return glfw.get_window_size(self.window)

    def init(self):
        pass

    def update(self, time):
        pass

    def render(self):
        pass

    def ui(self):
        pass

    def _on_key(self, window, key, scancode, action, mods):
        self.impl.keyboard_callback(window, key, scancode, action, mods)
        self.on_key(key, scancode, action, mods)

    def on_key(self, key, scancode, action, mods):
        pass

    def _on_char(self, window, codepoint):
        self.impl.char_callback(window, codepoint)
        self.on_char(codepoint)

    def on_char(self, codepoint):
        pass

    def _on_mouse_move(self, window, x, y):
        self.impl.mouse_callback(window, x, y)
        self.on_mouse_move(x, y)

    def on_mouse_move(self, x, y):
        pass

    def _on_mouse_button(self, window, button, action, mods):
        if not imgui.get_io().want_capture_mouse:
            self.on_mouse_button(button, action, mods)

    def on_mouse_button(self, button, action, mods):
        pass

    def _on_scroll(self, window, xoffset, yoffset):
        self.impl.scroll_callback(window, xoffset, yoffset)
        self.on_scroll(xoffset, yoffset)

    def on_scroll(self, xoffset, yoffset):
        pass

    def _on_resize(self, window, width, height):
        self.impl.resize_callback(window, width, height)
        self.on_resize(width, height)

    def on_resize(self, width, height):
        pass