text:
# -*- coding: utf-8 -*-
# flake8: noqa
import os

os.environ["PYOPENGL_PLATFORM"] = "egl"
import os.path as osp
import sys
import numpy as np
from OpenGL import GL
from OpenGL.GL import *  # noqa:F403

_CUR_DIR = osp.abspath(osp.dirname(__file__))
sys.path.insert(0, osp.join(_CUR_DIR, "../.."))
import lib.meshrenderer.gl_utils as gu
from lib.pysixd import misc
from lib.utils import logger

_RENDER_COLOR_ONLY = 0
_RENDER_PHONG_VERTEX = 1
_RENDER_TEXTURED = 2

__all__ = ["Renderer"]

class Renderer(object):
    """reconstruction models (with vertex color or texture) change shader
    phong/color during runtime."""

    MAX_FBO_WIDTH = 2000
    MAX_FBO_HEIGHT = 2000

    def __init__(
        self,
        models_cad_files,
        K=None,
        texture_paths=None,
        samples=1,
        vertex_tmp_store_folder=".cache",
        vertex_scale=1.0,
        height=480,
        width=640,
        near=0.25,
        far=6.0,
        offset_uv=0.0,
        render_uv=False,
        render_normalized_coords=False,
        render_nocs=False,
        use_cache=True,
        model_infos=None,
        cad_model_colors=None,
        recalculate_normals=False,
        model_load_fn="pysixd",
        # gpu_id=0,
    ):
        """vertex_scale: factor for point coordinates
        offset_uv: +0.5 to get more accurate pnp results,
                    0 to be consistent with bop_renderer
        """
        self._samples = samples
        self._context = gu.OffscreenContext()

        self.height = height
        self.width = width
        self.near = near
        self.far = far
        self.offset_uv = offset_uv

        self.K = K
        self.render_uv = render_uv
        self.render_normalized_coords = render_normalized_coords
        self.render_nocs = render_nocs

        # FBO
        W, H = Renderer.MAX_FBO_WIDTH, Renderer.MAX_FBO_HEIGHT
        self._fbo = gu.Framebuffer(
            {
                GL_COLOR_ATTACHMENT0: gu.Texture(GL_TEXTURE_2D, 1, GL_RGB8, W, H),
                GL_COLOR_ATTACHMENT1: gu.Texture(GL_TEXTURE_2D, 1, GL_R32F, W, H),
                GL_DEPTH_ATTACHMENT: gu.Renderbuffer(GL_DEPTH_COMPONENT32F, W, H),
            }
        )

        self._fbo_depth = gu.Framebuffer(
            {
                GL_COLOR_ATTACHMENT0: gu.Texture(GL_TEXTURE_2D, 1, GL_RGB8, W, H),
                GL_COLOR_ATTACHMENT1: gu.Texture(GL_TEXTURE_2D, 1, GL_R32F, W, H),
                GL_DEPTH_ATTACHMENT: gu.Renderbuffer(GL_DEPTH_COMPONENT32F, W, H),
            }
        )
        glNamedFramebufferDrawBuffers(
            self._fbo.id,
            2,
            np.array((GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1), dtype=np.uint32),
        )
        glNamedFramebufferDrawBuffers(
            self._fbo_depth.id,
            2,
            np.array((GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1), dtype=np.uint32),
        )

        if self._samples > 1:
            self._render_fbo = gu.Framebuffer(
                {
                    GL_COLOR_ATTACHMENT0: gu.TextureMultisample(self._samples, GL_RGB8, W, H, True),
                    GL_COLOR_ATTACHMENT1: gu.TextureMultisample(self._samples, GL_R32F, W, H, True),
                    GL_DEPTH_STENCIL_ATTACHMENT: gu.RenderbufferMultisample(self._samples, GL_DEPTH32F_STENCIL8, W, H),
                }
            )
            glNamedFramebufferDrawBuffers(
                self._render_fbo.id,
                2,
                np.array(
                    (GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1),
                    dtype=np.uint32,
                ),
            )

        self._fbo.bind()

        # VAO
        # maybe assign uv or normalized coords to color
        if model_load_fn == "pysixd":
            attributes = gu.geo.load_meshes_sixd(
                models_cad_files,
                vertex_tmp_store_folder,
                recalculate_normals=False,
                render_uv=render_uv,
                render_normalized_coords=render_normalized_coords,
                render_nocs=render_nocs,
                use_cache=use_cache,
                model_infos=model_infos,
                cad_model_colors=cad_model_colors,
                texture_paths=texture_paths,
            )
        elif model_load_fn == "pyassimp":
            attributes = gu.geo.load_meshes(
                models_cad_files,
                vertex_tmp_store_folder,
                recalculate_normals=recalculate_normals,
                use_cache=use_cache,
            )

        vertices = []
        indices = []
        self.textures = []
        self.is_cad_list = []
        self.is_textured_list = []
        for _i, attributes_dict in enumerate(attributes):
            vertex, normal, color, faces, texture_uv = [
                attributes_dict[_k]
                for _k in (
                    "vertices",
                    "normals",
                    "colors",
                    "faces",
                    "texture_uv",
                )
            ]
            indices.append(faces.flatten())
            self.is_cad_list.append(attributes_dict["is_cad"])
            self.is_textured_list.append(attributes_dict["is_textured"])

            if attributes_dict["is_textured"]:
                texture_path = texture_paths[_i]
                assert osp.exists(texture_path), texture_path
                self.textures.append(gu.loadTexture(texture_path))
            else:
                self.textures.append(None)

            if color.max() > 1.1:  # in range [0, 1]
                color = color / 255.0
            vertices.append(np.hstack((vertex * vertex_scale, normal, color, texture_uv)).flatten())

        indices = np.hstack(indices).astype(np.uint32)
        vertices = np.hstack(vertices).astype(np.float32)

        self.vao = gu.VAO(
            {
                (gu.Vertexbuffer(vertices), 0, 11 * 4): [
                    (0, 3, GL_FLOAT, GL_FALSE, 0 * 4),  # a_position
                    (1, 3, GL_FLOAT, GL_FALSE, 3 * 4),  # a_normal
                    (2, 3, GL_FLOAT, GL_FALSE, 6 * 4),  # a_color
                    (3, 2, GL_FLOAT, GL_TRUE, 9 * 4),  # a_texcoord
                ]
            },
            gu.EBO(indices),
        )
        self.vao.bind()

        # IBO
        vertex_count = [np.prod(vert["faces"].shape) for vert in attributes]
        instance_count = np.ones(len(attributes))
        first_index = [sum(vertex_count[:i]) for i in range(len(vertex_count))]

        vertex_sizes = [vert["vertices"].shape[0] for vert in attributes]
        base_vertex = [sum(vertex_sizes[:i]) for i in range(len(vertex_sizes))]
        base_instance = np.zeros(len(attributes))

        self.ibo = gu.IBO(
            vertex_count,
            instance_count,
            first_index,
            base_vertex,
            base_instance,
        )
        self.ibo.bind()

        gu.Shader.shader_folder = osp.join(_CUR_DIR, "shader")
        # if clamp:
        #     shader = gu.Shader('depth_shader_phong.vs', 'depth_shader_phong_clamped.frag')
        # else:
        textureless_texture_shader = gu.Shader(
            "depth_shader_textureless_texture.vs",
            "depth_shader_textureless_texture.frag",
        )
        textureless_texture_shader.compile_and_use()

        self.shaders = {"textureless_texture": textureless_texture_shader}
        self.used_shader = "textureless_texture"

        self._scene_buffer = gu.ShaderStorage(
            0,
            gu.Camera(offset_u=self.offset_uv, offset_v=self.offset_uv).data,
            True,
        )
        self._scene_buffer.bind()

        glEnable(GL_DEPTH_TEST)
        glClearColor(0.0, 0.0, 0.0, 1.0)

    def set_light_pose(self, direction=(0.4, 0.4, 0.4)):
        glUniform3f(0, direction[0], direction[1], direction[2])

    def set_ambient_light(self, a=0.4):
        glUniform1f(1, a)

    def set_diffuse_light(self, a=0.8):
        glUniform1f(2, a)

    def set_specular_light(self, a=0.3):
        glUniform1f(3, a)

    def set_render_type(self, value=_RENDER_PHONG_VERTEX):
        """
        0: color only
        1: phong vertex color
        2: textured
        """
        glUniform1i(4, value)

    def render(
        self,
        obj_id,
        R,
        t,
        K=None,
        W=640,
        H=480,
        near=None,
        far=None,
        random_light=False,
        phong={"ambient": 0.4, "diffuse": 0.8, "specular": 0.3},
        to_255=True,
        light_pose=None,
    ):
        """
        obj_id: the idx of models_cad_files, 0-based
        ----------
        Return:
            (bgr, depth)
        """
        if self.width != W:
            self.width = W
        if self.height != H:
            self.height = H
        if K is not None:
            self.K = K
        if near is not None:
            self.near = near
        if far is not None:
            self.far = far

        assert self.width <= Renderer.MAX_FBO_WIDTH and self.height <= Renderer.MAX_FBO_HEIGHT

        if self._samples > 1:
            self._render_fbo.bind()

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
        glViewport(0, 0, self.width, self.height)

        render_type = _RENDER_PHONG_VERTEX
        if self.is_textured_list[obj_id]:
            render_type = _RENDER_TEXTURED

            GL.glActiveTexture(GL.GL_TEXTURE0)  # Activate texture
            GL.glBindTexture(GL.GL_TEXTURE_2D, self.textures[obj_id])
            # shader = self.shaders[self.used_shader]
            GL.glUniform1i(5, 0)  # u_texture

        if (not random_light) and (light_pose is None) and (render_type != _RENDER_TEXTURED):
            render_type = _RENDER_COLOR_ONLY
        self.set_render_type(render_type)

        camera = gu.Camera(offset_u=self.offset_uv, offset_v=self.offset_uv)
        camera.realCamera(self.width, self.height, self.K, R, t, self.near, self.far)
        self._scene_buffer.update(camera.data)

        # print(phong)
        if random_light:
            self.set_light_pose(1000.0 * np.random.uniform(-1, 1, 3))  # this should be more random
            # self.set_light_pose(1000.0 * np.random.random(3))  # it is in [0,1], AAE default
            # originally there is not + 0.1 * (2 * np.random.rand() - 1)) for ambient
            self.set_ambient_light(phong["ambient"] + 0.1 * (2 * np.random.rand() - 1))
            self.set_diffuse_light(phong["diffuse"] + 0.1 * (2 * np.random.rand() - 1))
            self.set_specular_light(phong["specular"] + 0.1 * (2 * np.random.rand() - 1))
            # self.set_ambient_light(phong['ambient'])
            # self.set_diffuse_light(0.7)
            # self.set_specular_light(0.3)
        else:
            if light_pose is not None:
                # NOTE: fixed light (or assign random light outside)
                # default is (0.4, 0.4, 0.4)
                self.set_light_pose(np.array(light_pose) * 1000)
            else:
                self.set_light_pose(np.array([1.0, 1.0, 1.0]) * 1000)
            self.set_ambient_light(phong["ambient"])
            self.set_diffuse_light(phong["diffuse"])
            self.set_specular_light(phong["specular"])

        glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, ctypes.c_void_p(obj_id * 4 * 5))

        # if self._samples > 1:
        #     for i in range(2):
        #         glNamedFramebufferReadBuffer(self._render_fbo.id, GL_COLOR_ATTACHMENT0 + i)
        #         glNamedFramebufferDrawBuffer(self._fbo.id, GL_COLOR_ATTACHMENT0 + i)
        #         glBlitNamedFramebuffer(self._render_fbo.id, self._fbo.id, 0, 0, W, H, 0, 0, W, H, GL_COLOR_BUFFER_BIT, GL_NEAREST)
        #     self._fbo.bind()

        if self._samples > 1:
            self._fbo.bind()
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glNamedFramebufferDrawBuffer(self._fbo.id, GL_COLOR_ATTACHMENT1)
            glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, ctypes.c_void_p(obj_id * 4 * 5))

            glNamedFramebufferReadBuffer(self._render_fbo.id, GL_COLOR_ATTACHMENT0)
            glNamedFramebufferDrawBuffer(self._fbo.id, GL_COLOR_ATTACHMENT0)
            glBlitNamedFramebuffer(
                self._render_fbo.id,
                self._fbo.id,
                0,
                0,
                self.width,
                self.height,
                0,
                0,
                self.width,
                self.height,
                GL_COLOR_BUFFER_BIT,
                GL_NEAREST,
            )

            glNamedFramebufferDrawBuffers(
                self._fbo.id,
                2,
                np.array(
                    (GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1),
                    dtype=np.uint32,
                ),
            )

        glNamedFramebufferReadBuffer(self._fbo.id, GL_COLOR_ATTACHMENT0)
        if to_255:
            bgr_flipped = np.frombuffer(
                glReadPixels(0, 0, self.width, self.height, GL_BGR, GL_UNSIGNED_BYTE),
                dtype=np.uint8,
            ).reshape(self.height, self.width, 3)
        else:
            bgr_flipped = np.frombuffer(
                glReadPixels(0, 0, self.width, self.height, GL_BGR, GL_FLOAT),
                dtype=np.float32,
            ).reshape(self.height, self.width, 3)
        bgr = np.flipud(bgr_flipped).copy()

        glNamedFramebufferReadBuffer(self._fbo.id, GL_COLOR_ATTACHMENT1)
        depth_flipped = glReadPixels(0, 0, self.width, self.height, GL_RED, GL_FLOAT).reshape(self.height, self.width)
        depth = np.flipud(depth_flipped).copy()

        return bgr, depth

    def render_many(
        self,
        obj_ids,
        Rs,
        ts,
        K=None,
        W=640,
        H=480,
        near=None,
        far=None,
        random_light=False,
        phong={"ambient": 0.4, "diffuse": 0.8, "specular": 0.3},
        to_255=True,
        # to_bgr=True,
        light_pose=None,
        with_mask=True,
    ):
        """
        TODO: how to get masks
        """
        from lib.utils.mask_utils import mask2bbox_xyxy

        if self.width != W:
            self.width = W
        if self.height != H:
            self.height = H
        if K is not None:
            self.K = K
        if near is not None:
            self.near = near
        if far is not None:
            self.far = far
        assert self.width <= Renderer.MAX_FBO_WIDTH and self.height <= Renderer.MAX_FBO_HEIGHT

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glViewport(0, 0, self.width, self.height)

        if random_light:
            self.set_light_pose(1000.0 * np.random.uniform(-1, 1, 3))
            # self.set_light_pose(1000.0 * np.random.random(3))  # original
            self.set_ambient_light(phong["ambient"] + 0.1 * (2 * np.random.rand() - 1))
            self.set_diffuse_light(phong["diffuse"] + 0.1 * (2 * np.random.rand() - 1))
            self.set_specular_light(phong["specular"] + 0.1 * (2 * np.random.rand() - 1))
            # self.set_ambient_light(phong['ambient'])
            # self.set_diffuse_light(0.7)
            # self.set_specular_light(0.3)
        else:
            if light_pose is not None:
                # self.set_light_pose(np.array([400.0, 400.0, 400]))
                self.set_light_pose(np.array(light_pose) * 1000)
            else:
                self.set_light_pose(np.array([400.0, 400.0, 400]))
            self.set_ambient_light(phong["ambient"])
            self.set_diffuse_light(phong["diffuse"])
            self.set_specular_light(phong["specular"])

        bbs = []  # xyxy, NOTE: original AAE is xywh
        if with_mask:
            masks = []
        else:
            masks = None
        for i in range(len(obj_ids)):
            o = int(obj_ids[i])
            R = Rs[i]
            t = ts[i]
            camera = gu.Camera(offset_u=self.offset_uv, offset_v=self.offset_uv)
            camera.realCamera(self.width, self.height, self.K, R, t, self.near, self.far)
            self._scene_buffer.update(camera.data)

            render_type = _RENDER_PHONG_VERTEX
            if self.is_textured_list[o]:
                render_type = _RENDER_TEXTURED
            if (not random_light) and (light_pose is None) and (render_type != _RENDER_TEXTURED):
                render_type = _RENDER_COLOR_ONLY
            self.set_render_type(render_type)

            if self.is_textured_list[o]:
                GL.glActiveTexture(GL.GL_TEXTURE0)  # Activate texture
                GL.glBindTexture(GL.GL_TEXTURE_2D, self.textures[o])
                # shader = self.shaders[self.used_shader]
                GL.glUniform1i(5, 0)  # u_texture

            self._fbo.bind()
            # logger.info('{}'.format(type(o)))
            glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, ctypes.c_void_p(o * 4 * 5))

            self._fbo_depth.bind()
            glViewport(0, 0, self.width, self.height)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, ctypes.c_void_p(o * 4 * 5))

            glNamedFramebufferReadBuffer(self._fbo_depth.id, GL_COLOR_ATTACHMENT1)
            depth_flipped = glReadPixels(0, 0, self.width, self.height, GL_RED, GL_FLOAT).reshape(
                self.height, self.width
            )
            depth = np.flipud(depth_flipped).copy()

            mask = (depth > 0).astype(np.uint8)
            if with_mask:
                masks.append(mask)
            obj_bb_xyxy = mask2bbox_xyxy(mask)
            bbs.append(obj_bb_xyxy)

        glBindFramebuffer(GL_FRAMEBUFFER, self._fbo.id)
        glNamedFramebufferReadBuffer(self._fbo.id, GL_COLOR_ATTACHMENT0)
        if to_255:
            bgr_flipped = np.frombuffer(
                glReadPixels(0, 0, self.width, self.height, GL_BGR, GL_UNSIGNED_BYTE),
                dtype=np.uint8,
            ).reshape(self.height, self.width, 3)
        else:
            bgr_flipped = np.frombuffer(
                glReadPixels(0, 0, self.width, self.height, GL_BGR, GL_FLOAT),
                dtype=np.float32,
            ).reshape(self.height, self.width, 3)
        bgr = np.flipud(bgr_flipped).copy()

        glNamedFramebufferReadBuffer(self._fbo.id, GL_COLOR_ATTACHMENT1)
        depth_flipped = glReadPixels(0, 0, self.width, self.height, GL_RED, GL_FLOAT).reshape(self.height, self.width)
        depth = np.flipud(depth_flipped).copy()
        # we can get semantic segmentation via the {t_z}s, however in most cases, we don't need that
        return bgr, depth, bbs, masks

    def close(self):
        self._context.close()
        # self.r.release()

def test_render_many():
    import math
    import time
    from tqdm import tqdm
    from lib.vis_utils.image import (
        grid_show,
        vis_image_mask_bbox_cv2,
        vis_image_bboxes_cv2,
    )
    from lib.pysixd import view_sampler, transform

    model_dir = "datasets/BOP_DATASETS/lm/models"
    K = np.array(
        [
            [572.4114, 0.0, 325.2611],
            [0.0, 573.57043, 242.04899],
            [0.0, 0.0, 1.0],
        ]
    )
    width = 640
    height = 480
    ZNEAR = 0.25
    ZFAR = 6.0
    IDX2CLASS = {
        1: "ape",
        2: "benchvise",
        3: "bowl",
        4: "camera",
        5: "can",
        6: "cat",
        7: "cup",
        8: "driller",
        9: "duck",
        10: "eggbox",
        11: "glue",
        12: "holepuncher",
        13: "iron",
        14: "lamp",
        15: "phone",
    }
    CLASS2IDX = {cls_name: idx for idx, cls_name in IDX2CLASS.items()}
    classes = IDX2CLASS.values()
    classes = sorted(classes)
    models_cad_files = [osp.join(model_dir, "obj_{:06d}.ply".format(cls_idx)) for cls_idx in IDX2CLASS]
    # obj_ids = [CLASS2IDX[cls_name] for cls_name in classes]
    renderer = Renderer(models_cad_files, K=K, samples=1, vertex_scale=0.001)
    # R = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32)
    min_objs = 5
    max_objs = 6
    azimuth_range = (0, 2 * math.pi)
    elev_range = (0 * math.pi, 0.5 * math.pi)
    min_n_views = 1000
    radius = 0.7
    all_views, _ = view_sampler.sample_views(min_n_views, radius, azimuth_range, elev_range)
    rotations = view_sampler.sample_rotations_phere(
        min_n_views=min_n_views,
        radius=0.7,
        azimuth_range=azimuth_range,
        elev_range=elev_range,
        num_cyclo=36,
    )
    # rotations = view_sampler.sample_rotations_sphere_and_inplane(min_n_views, -90, (0, 360))
    print("num of sampled rotations: {}".format(len(rotations)))
    print(type(rotations))
    runs = 200
    t1 = time.perf_counter()
    dt_sample = 0
    dt_render = 0
    for i in tqdm(range(runs)):
        t_sample_0 = time.perf_counter()
        Rs = []
        ts = []
        N = np.random.randint(min_objs, max_objs + 1)

        obj_ids = np.random.choice(len(models_cad_files), N)
        ts = []
        ts_norm = []
        Rs = []

        for i in range(N):
            rand_k = random.randint(0, len(rotations) - 1)
            R = rotations[rand_k]
            success = False
            trial = 0
            while not success:
                tz = np.random.triangular(radius - radius / 3, radius, radius + radius / 3)
                tx = np.random.uniform(-0.35 * tz * width / K[0, 0], 0.35 * tz * width / K[0, 0])
                ty = np.random.uniform(-0.35 * tz * height / K[1, 1], 0.35 * tz * height / K[1, 1])
                t = np.array([tx, ty, tz])
                # print('tx range: ', -0.35 * tz * width / K[0, 0], 0.35 * tz * width / K[0, 0])
                # print('ty range: ', -0.35 * tz * height / K[1, 1], 0.35 * tz * height / K[1, 1])
                # print(t)
                # R = transform.random_rotation_matrix()[:3, :3]
                # R = v['R']
                t_norm = t / np.linalg.norm(t)
                if len(ts_norm) > 0 and np.any(np.dot(np.array(ts_norm), t_norm.reshape(3, 1)) > 0.99):
                    success = False  # too close
                    trial += 1
                    if trial >= 1000:
                        ts_norm.append(t_norm)
                        ts.append(t)
                        Rs.append(R)
                        break
                else:
                    ts_norm.append(t_norm)
                    ts.append(t)
                    Rs.append(R)
                    success = True
        dt_sample += time.perf_counter() - t_sample_0
        t_render_0 = time.perf_counter()
        random_light = bool(random.randint(0, 1))
        bgr, depth, bbs, masks = renderer.render_many(obj_ids, Rs, ts, random_light=random_light, with_mask=True)
        dt_render += time.perf_counter() - t_render_0
        labels = [IDX2CLASS[1 + obj_id] for obj_id in obj_ids]
        if False:  # show
            img_vis = vis_image_mask_bbox_cv2(
                bgr,
                masks,
                bboxes=np.array(bbs),
                labels=labels,
                font_scale=0.5,
                text_color="green",
            )
            img_vis = vis_image_bboxes_cv2(
                bgr,
                bboxes=np.array(bbs),
                labels=labels,
                font_scale=0.5,
                text_color="green",
            )
            grid_show(
                [img_vis[:, :, [2, 1, 0]], depth],
                [
                    "img_vis_randlight:{}, num objs:{}".format(random_light, len(obj_ids)),
                    "depth",
                ],
                row=1,
                col=2,
            )
    t_total = dt_sample + dt_render
    print(
        "t sample {}s, t_render: {}s t_total: {}s, fps: {}".format(
            dt_sample / runs, dt_render / runs, t_total / runs, runs / t_total
        )
    )
    """
    t sample 0.00034781177411787213s, t_render: 0.018074159582029098s t_total: 0.01842197135614697s, fps: 54.283006995683124
    """

def test_render_ycbv():
    model_dir = "datasets/BOP_DATASETS/ycbv/models"

    K = np.array([[1066.778, 0.0, 312.9869], [0.0, 1067.487, 241.3109], [0.0, 0.0, 1.0]])
    width = 640
    height = 480
    ZNEAR = 0.25
    ZFAR = 6.0
    IDX2CLASS = {
        1: "002_master_chef_can",  # [1.3360, -0.5000, 3.5105]
        2: "003_cracker_box",  # [0.5575, 1.7005, 4.8050]
        3: "004_sugar_box",  # [-0.9520, 1.4670, 4.3645]
        4: "005_tomato_soup_can",  # [-0.0240, -1.5270, 8.4035]
        5: "006_mustard_bottle",  # [1.2995, 2.4870, -11.8290]
        6: "007_tuna_fish_can",  # [-0.1565, 0.1150, 4.2625]
        7: "008_pudding_box",  # [1.1645, -4.2015, 3.1190]
        8: "009_gelatin_box",  # [1.4460, -0.5915, 3.6085]
        9: "010_potted_meat_can",  # [2.4195, 0.3075, 8.0715]
        10: "011_banana",  # [-18.6730, 12.1915, -1.4635]
        11: "019_pitcher_base",  # [5.3370, 5.8855, 25.6115]
        12: "021_bleach_cleanser",  # [4.9290, -2.4800, -13.2920]
        13: "024_bowl",  # [-0.2270, 0.7950, -2.9675]
        14: "025_mug",  # [-8.4675, -0.6995, -1.6145]
        15: "035_power_drill",  # [9.0710, 20.9360, -2.1190]
        16: "036_wood_block",  # [1.4265, -2.5305, 17.1890]
        17: "037_scissors",  # [7.0535, -28.1320, 0.0420]
        18: "040_large_marker",  # [0.0460, -2.1040, 0.3500]
        19: "051_large_clamp",  # [10.5180, -1.9640, -0.4745]
        20: "052_extra_large_clamp",  # [-0.3950, -10.4130, 0.1620]
        21: "061_foam_brick",  # [-0.0805, 0.0805, -8.2435]
    }
    CLASS2IDX = {cls_name: idx for idx, cls_name in IDX2CLASS.items()}
    classes = IDX2CLASS.values()
    classes = sorted(classes)
    # image_tensor = torch.cuda.FloatTensor(height, width, 3).detach()
    # depth_tensor = torch.cuda.FloatTensor(height, width, 1).detach()
    models_cad_files = [osp.join(model_dir, "obj_{:06d}.ply".format(cls_idx)) for cls_idx in IDX2CLASS]
    texture_files = [osp.join(model_dir, "obj_{:06d}.png".format(cls_idx)) for cls_idx in IDX2CLASS]

    renderer = Renderer(
        models_cad_files,
        K=K,
        texture_paths=texture_files,
        samples=1,
        vertex_scale=0.001,
    )
    # R = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32)
    R1 = axangle2mat((1, 0, 0), angle=0.5 * np.pi)
    R2 = axangle2mat((0, 0, 1), angle=-0.7 * np.pi)
    R = np.dot(R1, R2)
    t = np.array([0, 0, 0.7], dtype=np.float32)

    # benchmark speed
    if True:
        run = 0
        start = time.perf_counter()
        for i in tqdm(range(300)):
            for obj_id, cls_name in enumerate(classes):
                bgr, depth = renderer.render(
                    obj_id,
                    W=width,
                    H=height,
                    K=K,
                    R=R,
                    t=t,
                    near=ZNEAR,
                    far=ZFAR,
                    to_255=True,
                )
                run += 1
        dt = time.perf_counter() - start
        print("total runs: {}, time: {}s, speed: {} fps".format(run, dt, run / dt))
        # total runs: 45000, time: 86.256765127182s, speed: 521.6982103797815 fps

    if True:
        for i in range(1):
            for obj_id, cls_name in enumerate(classes):
                # obj_id = 0
                # if cls_name != 'driller': continue
                print(obj_id, cls_name)
                use_light = True  # bool(random.randint(0, 1))
                random_light = False  # bool(random.randint(0, 1))
                print("use_light", use_light)
                print("random_light", random_light)
                if random_light:
                    light_pose = np.random.uniform(-1, 1, 3)
                else:
                    light_pose = np.ones(3)
                print("light_pose: ", light_pose)
                bgr, depth = renderer.render(
                    obj_id,
                    W=width,
                    H=height,
                    K=K,
                    R=R,
                    t=t,
                    near=ZNEAR,
                    far=ZFAR,
                    to_255=True,
                    random_light=random_light,
                    light_pose=light_pose,
                    # image_tensor=image_tensor,
                    # depth_tensor=depth_tensor,
                    # light_pose=(0.4, 0.4, 0.4) if use_light else None,
                )
                # mmcv.imwrite(bgr, 'tmp_opengl_color_{}.png'.format(cls_name))
                # mmcv.imwrite((depth * 1000).astype(np.uint16), 'opengl_depth_{}.png'.format(cls_name))
                # bgr = image_tensor[:, :, :3]
                # bgr = (bgr.cpu().numpy() + 0.5).astype(np.uint8)

                # depth = depth_tensor[:,:, 0]
                # depth = depth.cpu().numpy()

                print(bgr.shape, bgr.min(), bgr.max(), bgr.dtype)
                fig = plt.figure(frameon=False, dpi=200)
                plt.subplot(1, 2, 1)
                plt.imshow(bgr[:, :, [2, 1, 0]])
                plt.axis("off")
                if random_light:
                    plt.title("{} phong(random)".format(cls_name))
                elif use_light:
                    plt.title("{} phong".format(cls_name))
                else:
                    plt.title("{} color".format(cls_name))

                plt.subplot(1, 2, 2)
                plt.imshow(depth)
                plt.axis("off")
                plt.title("{} depth".format(cls_name))

                # plt.savefig('tmp_{}.png'.format(cls_name))
                plt.show()

def test_render_many_ycbv():
    import math
    import time
    from tqdm import tqdm
    from lib.vis_utils.image import (
        grid_show,
        vis_image_mask_bbox_cv2,
        vis_image_bboxes_cv2,
    )
    from lib.pysixd import view_sampler, transform

    SHOW = False
    model_dir = "datasets/BOP_DATASETS/ycbv/models"
    K = np.array([[1066.778, 0.0, 312.9869], [0.0, 1067.487, 241.3109], [0.0, 0.0, 1.0]])
    width = 640
    height = 480
    ZNEAR = 0.25
    ZFAR = 6.0
    IDX2CLASS = {
        1: "002_master_chef_can",  # [1.3360, -0.5000, 3.5105]
        2: "003_cracker_box",  # [0.5575, 1.7005, 4.8050]
        3: "004_sugar_box",  # [-0.9520, 1.4670, 4.3645]
        4: "005_tomato_soup_can",  # [-0.0240, -1.5270, 8.4035]
        5: "006_mustard_bottle",  # [1.2995, 2.4870, -11.8290]
        6: "007_tuna_fish_can",  # [-0.1565, 0.1150, 4.2625]
        7: "008_pudding_box",  # [1.1645, -4.2015, 3.1190]
        8: "009_gelatin_box",  # [1.4460, -0.5915, 3.6085]
        9: "010_potted_meat_can",  # [2.4195, 0.3075, 8.0715]
        10: "011_banana",  # [-18.6730, 12.1915, -1.4635]
        11: "019_pitcher_base",  # [5.3370, 5.8855, 25.6115]
        12: "021_bleach_cleanser",  # [4.9290, -2.4800, -13.2920]
        13: "024_bowl",  # [-0.2270, 0.7950, -2.9675]
        14: "025_mug",  # [-8.4675, -0.6995, -1.6145]
        15: "035_power_drill",  # [9.0710, 20.9360, -2.1190]
        16: "036_wood_block",  # [1.4265, -2.5305, 17.1890]
        17: "037_scissors",  # [7.0535, -28.1320, 0.0420]
        18: "040_large_marker",  # [0.0460, -2.1040, 0.3500]
        19: "051_large_clamp",  # [10.5180, -1.9640, -0.4745]
        20: "052_extra_large_clamp",  # [-0.3950, -10.4130, 0.1620]
        21: "061_foam_brick",  # [-0.0805, 0.0805, -8.2435]
    }
    CLASS2IDX = {cls_name: idx for idx, cls_name in IDX2CLASS.items()}
    classes = IDX2CLASS.values()
    classes = sorted(classes)
    models_cad_files = [osp.join(model_dir, "obj_{:06d}.ply".format(cls_idx)) for cls_idx in IDX2CLASS]
    texture_files = [osp.join(model_dir, "obj_{:06d}.png".format(cls_idx)) for cls_idx in IDX2CLASS]
    # obj_ids = [CLASS2IDX[cls_name] for cls_name in classes]
    renderer = Renderer(
        models_cad_files,
        K=K,
        texture_paths=texture_files,
        samples=1,
        vertex_scale=0.001,
    )
    # R = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32)
    min_objs = 5
    max_objs = 6
    azimuth_range = (0, 2 * math.pi)
    elev_range = (0 * math.pi, 0.5 * math.pi)
    min_n_views = 1000
    radius = 0.7
    all_views, _ = view_sampler.sample_views(min_n_views, radius, azimuth_range, elev_range)
    rotations = view_sampler.sample_rotations_phere(
        min_n_views=min_n_views,
        radius=0.7,
        azimuth_range=azimuth_range,
        elev_range=elev_range,
        num_cyclo=36,
    )
    # rotations = view_sampler.sample_rotations_sphere_and_inplane(min_n_views, -90, (0, 360))
    print("num of sampled rotations: {}".format(len(rotations)))
    print(type(rotations))
    runs = 200
    t1 = time.perf_counter()
    dt_sample = 0
    dt_render = 0
    for i in tqdm(range(runs)):
        t_sample_0 = time.perf_counter()
        Rs = []
        ts = []
        N = np.random.randint(min_objs, max_objs + 1)

        obj_ids = np.random.choice(len(models_cad_files), N)
        ts = []
        ts_norm = []
        Rs = []

        for i in range(N):
            rand_k = random.randint(0, len(rotations) - 1)
            R = rotations[rand_k]
            success = False
            trial = 0
            while not success:
                tz = np.random.triangular(radius - radius / 3, radius, radius + radius / 3)
                tx = np.random.uniform(-0.35 * tz * width / K[0, 0], 0.35 * tz * width / K[0, 0])
                ty = np.random.uniform(-0.35 * tz * height / K[1, 1], 0.35 * tz * height / K[1, 1])
                t = np.array([tx, ty, tz])
                # print('tx range: ', -0.35 * tz * width / K[0, 0], 0.35 * tz * width / K[0, 0])
                # print('ty range: ', -0.35 * tz * height / K[1, 1], 0.35 * tz * height / K[1, 1])
                # print(t)
                # R = transform.random_rotation_matrix()[:3, :3]
                # R = v['R']
                t_norm = t / np.linalg.norm(t)
                if len(ts_norm) > 0 and np.any(np.dot(np.array(ts_norm), t_norm.reshape(3, 1)) > 0.99):
                    success = False  # too close
                    trial += 1
                    if trial >= 1000:
                        ts_norm.append(t_norm)
                        ts.append(t)
                        Rs.append(R)
                        break
                else:
                    ts_norm.append(t_norm)
                    ts.append(t)
                    Rs.append(R)
                    success = True
        dt_sample += time.perf_counter() - t_sample_0
        t_render_0 = time.perf_counter()
        random_light = bool(random.randint(0, 1))
        bgr, depth, bbs, masks = renderer.render_many(obj_ids, Rs, ts, random_light=random_light, with_mask=True)
        dt_render += time.perf_counter() - t_render_0
        labels = [IDX2CLASS[1 + obj_id] for obj_id in obj_ids]
        if SHOW:
            img_vis = vis_image_mask_bbox_cv2(
                bgr,
                masks,
                bboxes=np.array(bbs),
                labels=labels,
                font_scale=0.5,
                text_color="green",
            )
            img_vis = vis_image_bboxes_cv2(
                bgr,
                bboxes=np.array(bbs),
                labels=labels,
                font_scale=0.5,
                text_color="green",
            )
            grid_show(
                [img_vis[:, :, [2, 1, 0]], depth],
                [
                    "img_vis_randlight:{}, num objs:{}".format(random_light, len(obj_ids)),
                    "depth",
                ],
                row=1,
                col=2,
            )
    t_total = dt_sample + dt_render
    print(
        "t sample {}s, t_render: {}s t_total: {}s, fps: {}".format(
            dt_sample / runs, dt_render / runs, t_total / runs, runs / t_total
        )
    )
    """
    t sample 0.0031757269194349646s, t_render: 0.024196420814841985s t_total: 0.02737214773427695s, fps: 36.53348687533728
    """

if __name__ == "__main__":
    import glob
    import matplotlib.pyplot as plt
    import mmcv
    import time
    from tqdm import tqdm
    import random
    from transforms3d.axangles import axangle2mat

    # test_render_many()
    # test_render_ycbv()
    # test_render_many_ycbv()
    # exit(0)

    model_dir = "datasets/BOP_DATASETS/lm/models"

    K = np.array(
        [
            [572.4114, 0.0, 325.2611],
            [0.0, 573.57043, 242.04899],
            [0.0, 0.0, 1.0],
        ]
    )
    width = 640
    height = 480
    ZNEAR = 0.25
    ZFAR = 6.0
    IDX2CLASS = {
        1: "ape",
        2: "benchvise",
        3: "bowl",
        4: "camera",
        5: "can",
        6: "cat",
        7: "cup",
        8: "driller",
        9: "duck",
        10: "eggbox",
        11: "glue",
        12: "holepuncher",
        13: "iron",
        14: "lamp",
        15: "phone",
    }
    CLASS2IDX = {cls_name: idx for idx, cls_name in IDX2CLASS.items()}
    classes = IDX2CLASS.values()
    classes = sorted(classes)
    # image_tensor = torch.cuda.FloatTensor(height, width, 3).detach()
    # depth_tensor = torch.cuda.FloatTensor(height, width, 1).detach()
    models_cad_files = [osp.join(model_dir, "obj_{:06d}.ply".format(cls_idx)) for cls_idx in IDX2CLASS]
    # obj_ids = [CLASS2IDX[cls_name] for cls_name in classes]
    renderer = Renderer(models_cad_files, K=K, samples=1, vertex_scale=0.001)
    # R = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32)
    R1 = axangle2mat((1, 0, 0), angle=0.5 * np.pi)
    R2 = axangle2mat((0, 0, 1), angle=-0.7 * np.pi)
    R = np.dot(R1, R2)
    t = np.array([0, 0, 0.7], dtype=np.float32)

    # benchmark speed
    if True:
        run = 0
        start = time.perf_counter()
        for i in tqdm(range(300)):
            for obj_id, cls_name in enumerate(classes):
                bgr, depth = renderer.render(
                    obj_id,
                    W=width,
                    H=height,
                    K=K,
                    R=R,
                    t=t,
                    near=ZNEAR,
                    far=ZFAR,
                    to_255=True,
                )
                run += 1
        dt = time.perf_counter() - start
        print("total runs: {}, time: {}s, speed: {} fps".format(run, dt, run / dt))
        # total runs: 45000, time: 86.256765127182s, speed: 521.6982103797815 fps

    if True:
        for i in range(1):
            for obj_id, cls_name in enumerate(classes):
                # obj_id = 0
                # if cls_name != 'driller': continue
                print(obj_id, cls_name)
                use_light = True  # bool(random.randint(0, 1))
                random_light = False  # bool(random.randint(0, 1))
                print("use_light", use_light)
                print("random_light", random_light)
                light_pose = np.random.uniform(-1, 1, 3)
                print("light_pose: ", light_pose)
                bgr, depth = renderer.render(
                    obj_id,
                    W=width,
                    H=height,
                    K=K,
                    R=R,
                    t=t,
                    near=ZNEAR,
                    far=ZFAR,
                    to_255=True,
                    random_light=random_light,
                    light_pose=light_pose,
                    # image_tensor=image_tensor,
                    # depth_tensor=depth_tensor,
                    # light_pose=(0.4, 0.4, 0.4) if use_light else None,
                )
                # mmcv.imwrite(bgr, 'tmp_opengl_color_{}.png'.format(cls_name))
                # mmcv.imwrite((depth * 1000).astype(np.uint16), 'opengl_depth_{}.png'.format(cls_name))
                # bgr = image_tensor[:, :, :3]
                # bgr = (bgr.cpu().numpy() + 0.5).astype(np.uint8)

                # depth = depth_tensor[:,:, 0]
                # depth = depth.cpu().numpy()

                print(bgr.shape, bgr.min(), bgr.max(), bgr.dtype)
                fig = plt.figure(frameon=False, dpi=200)
                plt.subplot(1, 2, 1)
                plt.imshow(bgr[:, :, [2, 1, 0]])
                plt.axis("off")
                if random_light:
                    plt.title("{} phong(random)".format(cls_name))
                elif use_light:
                    plt.title("{} phong".format(cls_name))
                else:
                    plt.title("{} color".format(cls_name))

                plt.subplot(1, 2, 2)
                plt.imshow(depth)
                plt.axis("off")
                plt.title("{} depth".format(cls_name))

                # plt.savefig('tmp_{}.png'.format(cls_name))
                plt.show()


text:
import random
import csv
import os
from pathlib import Path
from tabulate import tabulate
from abc import abstractmethod
import keras.layers as Kl
import keras.models as Km
import numpy as np
import matplotlib.pyplot as plt
import EasyBoard
import cardDeck

class MetroX():

    def __init__(self, player1, deckType, exp1=1):
        player1 = globals()[player1]
        self.player1 = player1(exploration_factor=exp1)
        self.deckType = deckType
        self.init_game()

    def play_game(self):
        self.print_game()
        while self.gameEnd is False:
            self.state = self.play_move()
            self.print_game()

    def play_to_learn(self, episodes):
        avgReward = 0

        try:
            os.remove("results.txt")
        except:
            pass

        for i in range(episodes):
            print('Episode number: ' + str(i))

            while self.gameEnd is False:
                self.state = self.play_move(learn=True)

            score = self.board.calculateScore()
            reward = self.board.calculateReward()
            avgReward = (avgReward*0.95) + (reward*0.05)

            print('Score: ' + str(score))
            print('Reward: ' + str(reward))
            print('Weighted Average Reward: ' + str(avgReward))

            log = open("results.txt", "a")
            log.write(str(score)+', ')
            log.close()

            # update last state
            self.state = self.play_move(learn=True)
            # update winning state
            self.state = self.play_move(learn=True)

            if i%300 == 0:
                self.player1.save_values()

            self.init_game()

        self.player1.save_values()

    def play_move(self, learn=False):
        if learn is True:
            board_state = self.player1.make_move_and_learn(self.board, self.cardDeck, self.gameEnd)
        else:
            board_state = self.player1.make_move(self.board, self.cardDeck, self.gameEnd)
        card_state = self.turnEnd()
        return board_state+card_state

    def print_game(self):
        print(self.board)
        print('Card:' + self.cardDeck.currentCard)

    def turnEnd(self):
        if self.board.gameOver():
            self.gameEnd = True
        else:
            self.cardDeck.nextCard();
        return self.cardDeck.getState()

    def init_game(self):
        self.board = EasyBoard.EasyBoard()
        self.gameEnd = False
        self.cardDeck = cardDeck.CardDeck()

class Player():

    def __init__(self, exploration_factor=1):
        self.print_value = False
        self.exp_factor = exploration_factor

    def make_move(self, board, cardDeck, gameEnd):
        idx = int(input('Choose station number: '))
        board.makeMove(idx, cardDeck.currentCard)
        return board.getState()+cardDeck.getState()

class Agent(Player):

    def __init__(self, exploration_factor=1):
        super().__init__(exploration_factor)
        self.epsilon = 0.1
        self.alpha = 0.5
        self.prev_state = EasyBoard.EasyBoard().getState() + cardDeck.CardDeck().getState()
        self.state = None
        self.print_value = False

    @abstractmethod
    def calc_value(self, board, cardDeck):
        pass

    @abstractmethod
    def learn_state(self, board, cardDeck, gameEnd):
        pass

    def make_move(self, board, cardDeck, gameEnd):
        self.state = board.getState()+cardDeck.getState()

        if gameEnd:
            new_state = board.getState()+cardDeck.getState()
            return new_state

        p = random.uniform(0, 1)
        if p < self.exp_factor:
            new_state = self.make_optimal_move(board, cardDeck)
        else:
            moves = board.getValidMoves()
            idx = random.choice(moves)
            board.makeMove(idx, cardDeck.currentCard)
            new_state = board.getState()+cardDeck.getState()
        return new_state

    def make_move_and_learn(self, board, cardDeck, gameEnd):

        self.learn_state(board, cardDeck, gameEnd)

        return self.make_move(board, cardDeck, gameEnd)

    def make_optimal_move(self, board, cardDeck):
        moves = board.getValidMoves()
        if len(moves) == 1:
            board.makeMove(moves[0], cardDeck)
            return board.getState()+cardDeck.getState()

        temp_move_list = []
        v = -float('Inf')

        for idx in moves:

            v_temp = []
            temp_state = board.previewMove(idx, cardDeck.currentCard)

            simCardDraws = 50
            while simCardDraws > 0:
                simCardDraws-=1
                temp_state_op = temp_state+cardDeck.previewNextCard()
                v_temp.append(self.calc_value(temp_state_op))

            # deletes Nones
            v_temp = list(filter(None.__ne__, v_temp))

            if len(v_temp) != 0:
                v_temp = np.average(v_temp)
            else:
                # encourage exploration
                v_temp = 50

            if v_temp > v:
                temp_move_list = [idx]
                v = v_temp
            elif v_temp == v:
                temp_move_list.append(idx)

        try:
            board.makeMove(random.choice(temp_move_list), cardDeck)
        except ValueError:
            print('temp state:', temp_state_list)
            raise Exception('temp state empty')

        return board.getState()+cardDeck.getState()

    def reward(self, board):
        return board.calculateReward()

class DeepAgent(Agent):

    def __init__(self, exploration_factor=1):
        super().__init__(exploration_factor)
        self.value_model = self.load_model()

    @staticmethod
    def state2array(state):
        num_state = []
        for s in state:
            num_state.append(int(s))
        num_state = np.array([num_state])
        return num_state

    def learn_state(self, board, cardDeck, gameEnd):

        state = board.getState()+cardDeck.getState()

        target = self.calc_target(board, cardDeck, gameEnd)

        self.train_model(target, 10)

        self.prev_state = state

    def load_model(self):
        s = 'model_values_metroX.h5'
        model_file = Path(s)
        if model_file.is_file():
            model = Km.load_model(s)
            print('load model: ' + s)
        else:
            print('new model')
            model = Km.Sequential()
            model.add(Kl.Dense(180, activation='relu', input_dim=31))
            model.add(Kl.Dense(180, activation='relu'))
            model.add(Kl.Dense(1, activation='linear'))
            model.compile(optimizer='adam', loss='mean_absolute_error', metrics=['accuracy'])

        model.summary()
        return model

    def calc_value(self, state):
        return self.value_model.predict(self.state2array(state))

    def calc_target(self, board, cardDeck, gameEnd):
        v_s = self.calc_value(self.prev_state)

        R = self.reward(board)

        if gameEnd:
            v_s_tag = 0
        else:
            v_s_tag = self.calc_value(board.getState()+cardDeck.getState())

        target = np.array(v_s + self.alpha * (R + v_s_tag - v_s))

        return target

    def train_model(self, target, epochs):

        X_train = self.state2array(self.prev_state)

        if target is not None:
            self.value_model.fit(X_train, target, epochs=epochs, verbose=0)

    def save_values(self):
        s = 'model_values_metroX.h5'
        try:
            os.remove(s)
        except:
            pass
        self.value_model.save(s)

def check_player():
    #print('DeepAgent X 0.8 and DeepAgent 0.8')
    game = MetroX('DeepAgent', 'RandomDeck', 0.8)
    game.play_to_learn(10000)
    game = MetroX('DeepAgent', 'RandomDeck', 1)
    game.play_game()

check_player()

ext: py

reason: import numpy


text:
'''
This submodule collects useful functionality required across the task
submodules, such as preprocessing, validation, and common computations.
'''

import os
import inspect
import six

import numpy as np

def index_labels(labels, case_sensitive=False):
    """Convert a list of string identifiers into numerical indices.

    Parameters
    ----------
    labels : list of strings, shape=(n,)
        A list of annotations, e.g., segment or chord labels from an
        annotation file.

    case_sensitive : bool
        Set to True to enable case-sensitive label indexing
        (Default value = False)

    Returns
    -------
    indices : list, shape=(n,)
        Numerical representation of ``labels``
    index_to_label : dict
        Mapping to convert numerical indices back to labels.
        ``labels[i] == index_to_label[indices[i]]``

    """

    label_to_index = {}
    index_to_label = {}

    # If we're not case-sensitive,
    if not case_sensitive:
        labels = [str(s).lower() for s in labels]

    # First, build the unique label mapping
    for index, s in enumerate(sorted(set(labels))):
        label_to_index[s] = index
        index_to_label[index] = s

    # Remap the labels to indices
    indices = [label_to_index[s] for s in labels]

    # Return the converted labels, and the inverse mapping
    return indices, index_to_label

def generate_labels(items, prefix='__'):
    """Given an array of items (e.g. events, intervals), create a synthetic label
    for each event of the form '(label prefix)(item number)'

    Parameters
    ----------
    items : list-like
        A list or array of events or intervals
    prefix : str
        This prefix will be prepended to all synthetically generated labels
        (Default value = '__')

    Returns
    -------
    labels : list of str
        Synthetically generated labels

    """
    return ['{}{}'.format(prefix, n) for n in range(len(items))]

def intervals_to_samples(intervals, labels, offset=0, sample_size=0.1,
                         fill_value=None):
    """Convert an array of labeled time intervals to annotated samples.

    Parameters
    ----------
    intervals : np.ndarray, shape=(n, d)
        An array of time intervals, as returned by
        :func:`mir_eval.io.load_intervals()` or
        :func:`mir_eval.io.load_labeled_intervals()`.
        The ``i`` th interval spans time ``intervals[i, 0]`` to
        ``intervals[i, 1]``.

    labels : list, shape=(n,)
        The annotation for each interval

    offset : float > 0
        Phase offset of the sampled time grid (in seconds)
        (Default value = 0)

    sample_size : float > 0
        duration of each sample to be generated (in seconds)
        (Default value = 0.1)

    fill_value : type(labels[0])
        Object to use for the label with out-of-range time points.
        (Default value = None)

    Returns
    -------
    sample_times : list
        list of sample times

    sample_labels : list
        array of labels for each generated sample

    Notes
    -----
        Intervals will be rounded down to the nearest multiple
        of ``sample_size``.

    """

    # Round intervals to the sample size
    num_samples = int(np.floor(intervals.max() / sample_size))
    sample_indices = np.arange(num_samples, dtype=np.float32)
    sample_times = (sample_indices*sample_size + offset).tolist()
    sampled_labels = interpolate_intervals(
        intervals, labels, sample_times, fill_value)

    return sample_times, sampled_labels

def interpolate_intervals(intervals, labels, time_points, fill_value=None):
    """Assign labels to a set of points in time given a set of intervals.

    Time points that do not lie within an interval are mapped to `fill_value`.

    Parameters
    ----------
    intervals : np.ndarray, shape=(n, 2)
        An array of time intervals, as returned by
        :func:`mir_eval.io.load_intervals()`.
        The ``i`` th interval spans time ``intervals[i, 0]`` to
        ``intervals[i, 1]``.

        Intervals are assumed to be disjoint.

    labels : list, shape=(n,)
        The annotation for each interval

    time_points : array_like, shape=(m,)
        Points in time to assign labels.  These must be in
        non-decreasing order.

    fill_value : type(labels[0])
        Object to use for the label with out-of-range time points.
        (Default value = None)

    Returns
    -------
    aligned_labels : list
        Labels corresponding to the given time points.

    Raises
    ------
    ValueError
        If `time_points` is not in non-decreasing order.
    """

    # Verify that time_points is sorted
    time_points = np.asarray(time_points)

    if np.any(time_points[1:] < time_points[:-1]):
        raise ValueError('time_points must be in non-decreasing order')

    aligned_labels = [fill_value] * len(time_points)

    starts = np.searchsorted(time_points, intervals[:, 0], side='left')
    ends = np.searchsorted(time_points, intervals[:, 1], side='right')

    for (start, end, lab) in zip(starts, ends, labels):
        aligned_labels[start:end] = [lab] * (end - start)

    return aligned_labels

def sort_labeled_intervals(intervals, labels=None):
    '''Sort intervals, and optionally, their corresponding labels
    according to start time.

    Parameters
    ----------
    intervals : np.ndarray, shape=(n, 2)
        The input intervals

    labels : list, optional
        Labels for each interval

    Returns
    -------
    intervals_sorted or (intervals_sorted, labels_sorted)
        Labels are only returned if provided as input
    '''

    idx = np.argsort(intervals[:, 0])

    intervals_sorted = intervals[idx]

    if labels is None:
        return intervals_sorted
    else:
        return intervals_sorted, [labels[_] for _ in idx]

def f_measure(precision, recall, beta=1.0):
    """Compute the f-measure from precision and recall scores.

    Parameters
    ----------
    precision : float in (0, 1]
        Precision
    recall : float in (0, 1]
        Recall
    beta : float > 0
        Weighting factor for f-measure
        (Default value = 1.0)

    Returns
    -------
    f_measure : float
        The weighted f-measure

    """

    if precision == 0 and recall == 0:
        return 0.0

    return (1 + beta**2)*precision*recall/((beta**2)*precision + recall)

def intervals_to_boundaries(intervals, q=5):
    """Convert interval times into boundaries.

    Parameters
    ----------
    intervals : np.ndarray, shape=(n_events, 2)
        Array of interval start and end-times
    q : int
        Number of decimals to round to. (Default value = 5)

    Returns
    -------
    boundaries : np.ndarray
        Interval boundary times, including the end of the final interval

    """

    return np.unique(np.ravel(np.round(intervals, decimals=q)))

def boundaries_to_intervals(boundaries):
    """Convert an array of event times into intervals

    Parameters
    ----------
    boundaries : list-like
        List-like of event times.  These are assumed to be unique
        timestamps in ascending order.

    Returns
    -------
    intervals : np.ndarray, shape=(n_intervals, 2)
        Start and end time for each interval
    """

    if not np.allclose(boundaries, np.unique(boundaries)):
        raise ValueError('Boundary times are not unique or not ascending.')

    intervals = np.asarray(list(zip(boundaries[:-1], boundaries[1:])))

    return intervals

def adjust_intervals(intervals,
                     labels=None,
                     t_min=0.0,
                     t_max=None,
                     start_label='__T_MIN',
                     end_label='__T_MAX'):
    """Adjust a list of time intervals to span the range ``[t_min, t_max]``.

    Any intervals lying completely outside the specified range will be removed.

    Any intervals lying partially outside the specified range will be cropped.

    If the specified range exceeds the span of the provided data in either
    direction, additional intervals will be appended.  If an interval is
    appended at the beginning, it will be given the label ``start_label``; if
    an interval is appended at the end, it will be given the label
    ``end_label``.

    Parameters
    ----------
    intervals : np.ndarray, shape=(n_events, 2)
        Array of interval start and end-times
    labels : list, len=n_events or None
        List of labels
        (Default value = None)
    t_min : float or None
        Minimum interval start time.
        (Default value = 0.0)
    t_max : float or None
        Maximum interval end time.
        (Default value = None)
    start_label : str or float or int
        Label to give any intervals appended at the beginning
        (Default value = '__T_MIN')
    end_label : str or float or int
        Label to give any intervals appended at the end
        (Default value = '__T_MAX')

    Returns
    -------
    new_intervals : np.ndarray
        Intervals spanning ``[t_min, t_max]``
    new_labels : list
        List of labels for ``new_labels``

    """

    # When supplied intervals are empty and t_max and t_min are supplied,
    # create one interval from t_min to t_max with the label start_label
    if t_min is not None and t_max is not None and intervals.size == 0:
        return np.array([[t_min, t_max]]), [start_label]
    # When intervals are empty and either t_min or t_max are not supplied,
    # we can't append new intervals
    elif (t_min is None or t_max is None) and intervals.size == 0:
        raise ValueError("Supplied intervals are empty, can't append new"
                         " intervals")

    if t_min is not None:
        # Find the intervals that end at or after t_min
        first_idx = np.argwhere(intervals[:, 1] >= t_min)

        if len(first_idx) > 0:
            # If we have events below t_min, crop them out
            if labels is not None:
                labels = labels[int(first_idx[0]):]
            # Clip to the range (t_min, +inf)
            intervals = intervals[int(first_idx[0]):]
        intervals = np.maximum(t_min, intervals)

        if intervals.min() > t_min:
            # Lowest boundary is higher than t_min:
            # add a new boundary and label
            intervals = np.vstack(([t_min, intervals.min()], intervals))
            if labels is not None:
                labels.insert(0, start_label)

    if t_max is not None:
        # Find the intervals that begin after t_max
        last_idx = np.argwhere(intervals[:, 0] > t_max)

        if len(last_idx) > 0:
            # We have boundaries above t_max.
            # Trim to only boundaries <= t_max
            if labels is not None:
                labels = labels[:int(last_idx[0])]
            # Clip to the range (-inf, t_max)
            intervals = intervals[:int(last_idx[0])]

        intervals = np.minimum(t_max, intervals)

        if intervals.max() < t_max:
            # Last boundary is below t_max: add a new boundary and label
            intervals = np.vstack((intervals, [intervals.max(), t_max]))
            if labels is not None:
                labels.append(end_label)

    return intervals, labels

def adjust_events(events, labels=None, t_min=0.0,
                  t_max=None, label_prefix='__'):
    """Adjust the given list of event times to span the range
    ``[t_min, t_max]``.

    Any event times outside of the specified range will be removed.

    If the times do not span ``[t_min, t_max]``, additional events will be
    added with the prefix ``label_prefix``.

    Parameters
    ----------
    events : np.ndarray
        Array of event times (seconds)
    labels : list or None
        List of labels
        (Default value = None)
    t_min : float or None
        Minimum valid event time.
        (Default value = 0.0)
    t_max : float or None
        Maximum valid event time.
        (Default value = None)
    label_prefix : str
        Prefix string to use for synthetic labels
        (Default value = '__')

    Returns
    -------
    new_times : np.ndarray
        Event times corrected to the given range.

    """
    if t_min is not None:
        first_idx = np.argwhere(events >= t_min)

        if len(first_idx) > 0:
            # We have events below t_min
            # Crop them out
            if labels is not None:
                labels = labels[int(first_idx[0]):]
            events = events[int(first_idx[0]):]

        if events[0] > t_min:
            # Lowest boundary is higher than t_min:
            # add a new boundary and label
            events = np.concatenate(([t_min], events))
            if labels is not None:
                labels.insert(0, '%sT_MIN' % label_prefix)

    if t_max is not None:
        last_idx = np.argwhere(events > t_max)

        if len(last_idx) > 0:
            # We have boundaries above t_max.
            # Trim to only boundaries <= t_max
            if labels is not None:
                labels = labels[:int(last_idx[0])]
            events = events[:int(last_idx[0])]

        if events[-1] < t_max:
            # Last boundary is below t_max: add a new boundary and label
            events = np.concatenate((events, [t_max]))
            if labels is not None:
                labels.append('%sT_MAX' % label_prefix)

    return events, labels

def intersect_files(flist1, flist2):
    """Return the intersection of two sets of filepaths, based on the file name
    (after the final '/') and ignoring the file extension.

    Examples
    --------
     >>> flist1 = ['/a/b/abc.lab', '/c/d/123.lab', '/e/f/xyz.lab']
     >>> flist2 = ['/g/h/xyz.npy', '/i/j/123.txt', '/k/l/456.lab']
     >>> sublist1, sublist2 = mir_eval.util.intersect_files(flist1, flist2)
     >>> print sublist1
     ['/e/f/xyz.lab', '/c/d/123.lab']
     >>> print sublist2
     ['/g/h/xyz.npy', '/i/j/123.txt']

    Parameters
    ----------
    flist1 : list
        first list of filepaths
    flist2 : list
        second list of filepaths

    Returns
    -------
    sublist1 : list
        subset of filepaths with matching stems from ``flist1``
    sublist2 : list
        corresponding filepaths from ``flist2``

    """
    def fname(abs_path):
        """Returns the filename given an absolute path.

        Parameters
        ----------
        abs_path :

        Returns
        -------

        """
        return os.path.splitext(os.path.split(abs_path)[-1])[0]

    fmap = dict([(fname(f), f) for f in flist1])
    pairs = [list(), list()]
    for f in flist2:
        if fname(f) in fmap:
            pairs[0].append(fmap[fname(f)])
            pairs[1].append(f)

    return pairs

def merge_labeled_intervals(x_intervals, x_labels, y_intervals, y_labels):
    r"""Merge the time intervals of two sequences.

    Parameters
    ----------
    x_intervals : np.ndarray
        Array of interval times (seconds)
    x_labels : list or None
        List of labels
    y_intervals : np.ndarray
        Array of interval times (seconds)
    y_labels : list or None
        List of labels

    Returns
    -------
    new_intervals : np.ndarray
        New interval times of the merged sequences.
    new_x_labels : list
        New labels for the sequence ``x``
    new_y_labels : list
        New labels for the sequence ``y``

    """
    align_check = [x_intervals[0, 0] == y_intervals[0, 0],
                   x_intervals[-1, 1] == y_intervals[-1, 1]]
    if False in align_check:
        raise ValueError(
            "Time intervals do not align; did you mean to call "
            "'adjust_intervals()' first?")
    time_boundaries = np.unique(
        np.concatenate([x_intervals, y_intervals], axis=0))
    output_intervals = np.array(
        [time_boundaries[:-1], time_boundaries[1:]]).T

    x_labels_out, y_labels_out = [], []
    x_label_range = np.arange(len(x_labels))
    y_label_range = np.arange(len(y_labels))
    for t0, _ in output_intervals:
        x_idx = x_label_range[(t0 >= x_intervals[:, 0])]
        x_labels_out.append(x_labels[x_idx[-1]])
        y_idx = y_label_range[(t0 >= y_intervals[:, 0])]
        y_labels_out.append(y_labels[y_idx[-1]])
    return output_intervals, x_labels_out, y_labels_out

def _bipartite_match(graph):
    """Find maximum cardinality matching of a bipartite graph (U,V,E).
    The input format is a dictionary mapping members of U to a list
    of their neighbors in V.

    The output is a dict M mapping members of V to their matches in U.

    Parameters
    ----------
    graph : dictionary : left-vertex -> list of right vertices
        The input bipartite graph.  Each edge need only be specified once.

    Returns
    -------
    matching : dictionary : right-vertex -> left vertex
        A maximal bipartite matching.

    """
    # Adapted from:
    #
    # Hopcroft-Karp bipartite max-cardinality matching and max independent set
    # David Eppstein, UC Irvine, 27 Apr 2002

    # initialize greedy matching (redundant, but faster than full search)
    matching = {}
    for u in graph:
        for v in graph[u]:
            if v not in matching:
                matching[v] = u
                break

    while True:
        # structure residual graph into layers
        # pred[u] gives the neighbor in the previous layer for u in U
        # preds[v] gives a list of neighbors in the previous layer for v in V
        # unmatched gives a list of unmatched vertices in final layer of V,
        # and is also used as a flag value for pred[u] when u is in the first
        # layer
        preds = {}
        unmatched = []
        pred = dict([(u, unmatched) for u in graph])
        for v in matching:
            del pred[matching[v]]
        layer = list(pred)

        # repeatedly extend layering structure by another pair of layers
        while layer and not unmatched:
            new_layer = {}
            for u in layer:
                for v in graph[u]:
                    if v not in preds:
                        new_layer.setdefault(v, []).append(u)
            layer = []
            for v in new_layer:
                preds[v] = new_layer[v]
                if v in matching:
                    layer.append(matching[v])
                    pred[matching[v]] = v
                else:
                    unmatched.append(v)

        # did we finish layering without finding any alternating paths?
        if not unmatched:
            unlayered = {}
            for u in graph:
                for v in graph[u]:
                    if v not in preds:
                        unlayered[v] = None
            return matching

        def recurse(v):
            """Recursively search backward through layers to find alternating
            paths.  recursion returns true if found path, false otherwise
            """
            if v in preds:
                L = preds[v]
                del preds[v]
                for u in L:
                    if u in pred:
                        pu = pred[u]
                        del pred[u]
                        if pu is unmatched or recurse(pu):
                            matching[v] = u
                            return True
            return False

        for v in unmatched:
            recurse(v)

def _outer_distance_mod_n(ref, est, modulus=12):
    """Compute the absolute outer distance modulo n.
    Using this distance, d(11, 0) = 1 (modulo 12)

    Parameters
    ----------
    ref : np.ndarray, shape=(n,)
        Array of reference values.
    est : np.ndarray, shape=(m,)
        Array of estimated values.
    modulus : int
        The modulus.
        12 by default for octave equivalence.

    Returns
    -------
    outer_distance : np.ndarray, shape=(n, m)
        The outer circular distance modulo n.

    """
    ref_mod_n = np.mod(ref, modulus)
    est_mod_n = np.mod(est, modulus)
    abs_diff = np.abs(np.subtract.outer(ref_mod_n, est_mod_n))
    return np.minimum(abs_diff, modulus - abs_diff)

def match_events(ref, est, window, distance=None):
    """Compute a maximum matching between reference and estimated event times,
    subject to a window constraint.

    Given two lists of event times ``ref`` and ``est``, we seek the largest set
    of correspondences ``(ref[i], est[j])`` such that
    ``distance(ref[i], est[j]) <= window``, and each
    ``ref[i]`` and ``est[j]`` is matched at most once.

    This is useful for computing precision/recall metrics in beat tracking,
    onset detection, and segmentation.

    Parameters
    ----------
    ref : np.ndarray, shape=(n,)
        Array of reference values
    est : np.ndarray, shape=(m,)
        Array of estimated values
    window : float > 0
        Size of the window.
    distance : function
        function that computes the outer distance of ref and est.
        By default uses ``|ref[i] - est[j]|``

    Returns
    -------
    matching : list of tuples
        A list of matched reference and event numbers.
        ``matching[i] == (i, j)`` where ``ref[i]`` matches ``est[j]``.

    """
    if distance is not None:
        # Compute the indices of feasible pairings
        hits = np.where(distance(ref, est) <= window)
    else:
        hits = _fast_hit_windows(ref, est, window)

    # Construct the graph input
    G = {}
    for ref_i, est_i in zip(*hits):
        if est_i not in G:
            G[est_i] = []
        G[est_i].append(ref_i)

    # Compute the maximum matching
    matching = sorted(_bipartite_match(G).items())

    return matching

def _fast_hit_windows(ref, est, window):
    '''Fast calculation of windowed hits for time events.

    Given two lists of event times ``ref`` and ``est``, and a
    tolerance window, computes a list of pairings
    ``(i, j)`` where ``|ref[i] - est[j]| <= window``.

    This is equivalent to, but more efficient than the following:

    >>> hit_ref, hit_est = np.where(np.abs(np.subtract.outer(ref, est))
    ...                             <= window)

    Parameters
    ----------
    ref : np.ndarray, shape=(n,)
        Array of reference values
    est : np.ndarray, shape=(m,)
        Array of estimated values
    window : float >= 0
        Size of the tolerance window

    Returns
    -------
    hit_ref : np.ndarray
    hit_est : np.ndarray
        indices such that ``|hit_ref[i] - hit_est[i]| <= window``
    '''

    ref = np.asarray(ref)
    est = np.asarray(est)
    ref_idx = np.argsort(ref)
    ref_sorted = ref[ref_idx]

    left_idx = np.searchsorted(ref_sorted, est - window, side='left')
    right_idx = np.searchsorted(ref_sorted, est + window, side='right')

    hit_ref, hit_est = [], []

    for j, (start, end) in enumerate(zip(left_idx, right_idx)):
        hit_ref.extend(ref_idx[start:end])
        hit_est.extend([j] * (end - start))

    return hit_ref, hit_est

def validate_intervals(intervals):
    """Checks that an (n, 2) interval ndarray is well-formed, and raises errors
    if not.

    Parameters
    ----------
    intervals : np.ndarray, shape=(n, 2)
        Array of interval start/end locations.

    """

    # Validate interval shape
    if intervals.ndim != 2 or intervals.shape[1] != 2:
        raise ValueError('Intervals should be n-by-2 numpy ndarray, '
                         'but shape={}'.format(intervals.shape))

    # Make sure no times are negative
    if (intervals < 0).any():
        raise ValueError('Negative interval times found')

    # Make sure all intervals have strictly positive duration
    if (intervals[:, 1] <= intervals[:, 0]).any():
        raise ValueError('All interval durations must be strictly positive')

def validate_events(events, max_time=30000.):
    """Checks that a 1-d event location ndarray is well-formed, and raises
    errors if not.

    Parameters
    ----------
    events : np.ndarray, shape=(n,)
        Array of event times
    max_time : float
        If an event is found above this time, a ValueError will be raised.
        (Default value = 30000.)

    """
    # Make sure no event times are huge
    if (events > max_time).any():
        raise ValueError('An event at time {} was found which is greater than '
                         'the maximum allowable time of max_time = {} (did you'
                         ' supply event times in '
                         'seconds?)'.format(events.max(), max_time))
    # Make sure event locations are 1-d np ndarrays
    if events.ndim != 1:
        raise ValueError('Event times should be 1-d numpy ndarray, '
                         'but shape={}'.format(events.shape))
    # Make sure event times are increasing
    if (np.diff(events) < 0).any():
        raise ValueError('Events should be in increasing order.')

def validate_frequencies(frequencies, max_freq, min_freq,
                         allow_negatives=False):
    """Checks that a 1-d frequency ndarray is well-formed, and raises
    errors if not.

    Parameters
    ----------
    frequencies : np.ndarray, shape=(n,)
        Array of frequency values
    max_freq : float
        If a frequency is found above this pitch, a ValueError will be raised.
        (Default value = 5000.)
    min_freq : float
        If a frequency is found below this pitch, a ValueError will be raised.
        (Default value = 20.)
    allow_negatives : bool
        Whether or not to allow negative frequency values.
    """
    # If flag is true, map frequencies to their absolute value.
    if allow_negatives:
        frequencies = np.abs(frequencies)
    # Make sure no frequency values are huge
    if (np.abs(frequencies) > max_freq).any():
        raise ValueError('A frequency of {} was found which is greater than '
                         'the maximum allowable value of max_freq = {} (did '
                         'you supply frequency values in '
                         'Hz?)'.format(frequencies.max(), max_freq))
    # Make sure no frequency values are tiny
    if (np.abs(frequencies) < min_freq).any():
        raise ValueError('A frequency of {} was found which is less than the '
                         'minimum allowable value of min_freq = {} (did you '
                         'supply frequency values in '
                         'Hz?)'.format(frequencies.min(), min_freq))
    # Make sure frequency values are 1-d np ndarrays
    if frequencies.ndim != 1:
        raise ValueError('Frequencies should be 1-d numpy ndarray, '
                         'but shape={}'.format(frequencies.shape))

def has_kwargs(function):
    r'''Determine whether a function has \*\*kwargs.

    Parameters
    ----------
    function : callable
        The function to test

    Returns
    -------
    True if function accepts arbitrary keyword arguments.
    False otherwise.
    '''

    if six.PY2:
        return inspect.getargspec(function).keywords is not None
    else:
        sig = inspect.signature(function)

        for param in sig.parameters.values():
            if param.kind == param.VAR_KEYWORD:
                return True

        return False

def filter_kwargs(_function, *args, **kwargs):
    """Given a function and args and keyword args to pass to it, call the function
    but using only the keyword arguments which it accepts.  This is equivalent
    to redefining the function with an additional \*\*kwargs to accept slop
    keyword args.

    If the target function already accepts \*\*kwargs parameters, no filtering
    is performed.

    Parameters
    ----------
    _function : callable
        Function to call.  Can take in any number of args or kwargs

    """

    if has_kwargs(_function):
        return _function(*args, **kwargs)

    # Get the list of function arguments
    func_code = six.get_function_code(_function)
    function_args = func_code.co_varnames[:func_code.co_argcount]
    # Construct a dict of those kwargs which appear in the function
    filtered_kwargs = {}
    for kwarg, value in list(kwargs.items()):
        if kwarg in function_args:
            filtered_kwargs[kwarg] = value
    # Call the function with the supplied args and the filtered kwarg dict
    return _function(*args, **filtered_kwargs)

def intervals_to_durations(intervals):
    """Converts an array of n intervals to their n durations.

    Parameters
    ----------
    intervals : np.ndarray, shape=(n, 2)
        An array of time intervals, as returned by
        :func:`mir_eval.io.load_intervals()`.
        The ``i`` th interval spans time ``intervals[i, 0]`` to
        ``intervals[i, 1]``.

    Returns
    -------
    durations : np.ndarray, shape=(n,)
        Array of the duration of each interval.

    """
    validate_intervals(intervals)
    return np.abs(np.diff(intervals, axis=-1)).flatten()

def hz_to_midi(freqs):
    '''Convert Hz to MIDI numbers

    Parameters
    ----------
    freqs : number or ndarray
        Frequency/frequencies in Hz

    Returns
    -------
    midi : number or ndarray
        MIDI note numbers corresponding to input frequencies.
        Note that these may be fractional.
    '''
    return 12.0 * (np.log2(freqs) - np.log2(440.0)) + 69.0

def midi_to_hz(midi):
    '''Convert MIDI numbers to Hz

    Parameters
    ----------
    midi : number or ndarray
        MIDI notes

    Returns
    -------
    freqs : number or ndarray
        Frequency/frequencies in Hz corresponding to `midi`
    '''
    return 440.0 * (2.0 ** ((midi - 69.0)/12.0))


text:
import pytest
import numpy as np

# from sklearn.utils import assert_array_equal
# from sklearn.utils import assert_allclose

from GLE_analysisEM import GLE_Estimator, GLE_BasisTransform, sufficient_stats, sufficient_stats_hidden, preprocessingTraj
from ..utils import loadTestDatas_est, generateRandomDefPosMat

@pytest.fixture
def data():
    return ["GLE_analysisEM/tests/0_trajectories.dat"]

def test_gen_random_mat():
    A = generateRandomDefPosMat()
    assert np.all(np.linalg.eigvals(A + A.T) > 0)

# def test_basis_proj(data):
#     transform = GLE_BasisTransform(dim_x=1)
#
#
# def test_user_input(data):
#     est = GLE_Estimator(init_params="user")
#
#
# def test_markov_input(data):
#     est = GLE_Estimator(init_params="markov")
#

def test_em_estimator(data):
    est = GLE_Estimator(verbose=1, C_init=np.identity(2))
    X, idx, Xh = loadTestDatas_est(data, 1, 1)
    basis = GLE_BasisTransform()
    X = basis.fit_transform(X)
    est.fit(X)
    assert est.dt == 5e-3
    assert hasattr(est, "converged_")

    # X = data[0]
    # assert_array_equal(y_pred, np.ones(X.shape[0], dtype=np.int64))


text:
import unittest
from unittest.mock import MagicMock, patch

from flask import url_for
import numpy as np

import discover
import load
import models

import api

class ApiTestCase(unittest.TestCase):
    def setUp(self):
        api.app.testing = True
        self.app = api.app.test_client()

def build_metadata(name: str) -> models.DatasetMetadata:
    return models.DatasetMetadata(
        dataset_id=load.name_to_id(name), name=name, spectra_number=1234,
        x_range=models.CoordinatesRange(np.array([1, 2, 3])),
        y_range=models.CoordinatesRange(np.array([4, 5, 6])))

dataset_discovery = MagicMock(return_value=['first_dataset', 'second_dataset'])
metadata_loading = MagicMock(side_effect=build_metadata)
some_id = 1234

def get_name_for_matching_id(identifier: int, _) -> str:
    if identifier == some_id:
        return 'first_dataset'
    raise KeyError(identifier)

name_discovery = MagicMock(side_effect=get_name_for_matching_id)

@patch.object(discover, 'datasets', new=dataset_discovery)
@patch.object(load, 'metadata', new=metadata_loading)
class TestPreparationsList(ApiTestCase):
    def test_gets_list_of_preparations_if_default_store(self):
        response = self.app.get('/preparations/')
        self.assertIn(b'first_dataset', response.data)
        self.assertIn(b'second_dataset', response.data)

    @patch.object(discover, 'name', new=name_discovery)
    def test_gets_single_preparation_when_passed_id(self):
        response = self.app.get('/preparations/%i' % some_id)
        self.assertIn(b'first_dataset', response.data)
        self.assertNotIn(b'second_dataset', response.data)

    @patch.object(discover, 'name', new=name_discovery)
    def test_has_backward_compatible_response(self):
        response = self.app.get('/preparations/%i' % some_id)
        self.assertIn(b'"Id": ', response.data)
        self.assertIn(b'"Name": ', response.data)
        self.assertIn(b'"SpectraNumber": ', response.data)
        self.assertIn(b'"XRange": ', response.data)
        self.assertIn(b'"YRange": ', response.data)
        self.assertIn(b'"Min": ', response.data)
        self.assertIn(b'"Max": ', response.data)

heatmap_loading = MagicMock(return_value=models.Heatmap(
    Mz=1., Intensities=[1., 2., 3.], X=[1, 2, 3], Y=[4, 5, 6]))

@patch.object(load, 'heatmap', new=heatmap_loading)
class TestHeatmap(ApiTestCase):
    url = '/heatmap/123?channelId=1'

    def test_has_mz(self):
        response = self.app.get(self.url)
        self.assertIn(b'"Mz": ', response.data)

    def test_has_intensities(self):
        response = self.app.get(self.url)
        self.assertIn(b'"Intensities": ', response.data)

    def test_has_coordinates(self):
        response = self.app.get(self.url)
        self.assertIn(b'"X": ', response.data)
        self.assertIn(b'"Y": ', response.data)

spectrum_loading = MagicMock(side_effect=lambda _, x, y: models.Spectrum(
    Intensities=[1., 2., 3.], Id=1, Mz=[4., 5., 6.], X=x, Y=y))

@patch.object(load, 'spectrum', new=spectrum_loading)
class TestSpectrum(ApiTestCase):
    url = '/spectrum/123?x=8&y=9'

    def test_throws_404_without_coordinates(self):
        response = self.app.get('/spectrum/123?x=8')
        self.assertEqual(response.status_code, 404)
        response = self.app.get('/spectrum/123?y=9')
        self.assertEqual(response.status_code, 404)

    def test_has_id(self):
        response = self.app.get(self.url)
        self.assertIn(b'"Id": 1', response.data)

    def test_has_intensities(self):
        response = self.app.get(self.url)
        self.assertIn(b'"Intensities": ', response.data)

    def test_has_mz(self):
        response = self.app.get(self.url)
        self.assertIn(b'"Mz": ', response.data)

    def test_has_coordinates(self):
        response = self.app.get(self.url)
        self.assertIn(b'"X": 8', response.data)
        self.assertIn(b'"Y": 9', response.data)

max_stars_repo_name: spectre-team/spectre-visualization

max_issues_repo_name: spectre-team/spectre-visualization

max_forks_repo_name: spectre-team/spectre-visualization


text:
# Derive the inertia of a uniform density torus
from sympy import Matrix, symbols, collect, integrate, pi
from pydy import *
Int = integrate
a, b, c, r1, r2, rho, m = symbols('a b c r1 r2 rho m')

# Volume of a torus
V = 2*pi**2*r1*r2**2

# A is the frame fixed in the torus, think of these as the global coordinates
# A[1] and A[3] lie in the plane of symmetry, A[3] = A[1] x A[2]
A = NewtonianReferenceFrame('A')
# B is a frame that rotates about the A[3] axis by an angle a
B = A.rotate('B', 2, a)
# C rotates about an axis along the tangent to the center of the circular cross
# section by an angle b
C = B.rotate('C', 3, b)

# Locate an arbitrary point on the torus
p = Vector(r1*B[1] + c*C[2])

# Express it in the A reference frame
x = dot(p, A[1])
y = dot(p, A[2])
z = dot(p, A[3])

# Determinant of the Jacobian of the mapping from a, b, c to x, y, z
X = [a, b, c]
F = Matrix([x, y, z])
J = F.jacobian(X)
dv = (J.det(method='berkowitz')).expand().subs({cos(a)**2:1-sin(a)**2,
        cos(b)**2:1-sin(b)**2}).expand()

print 'dx*dy*dz =(', dv, ')*da*db*dc'

# Integrands of definition of inertia scalars
i11 = rho * dot(cross(p, A[1]), cross(p, A[1])) * dv
i22 = rho * dot(cross(p, A[2]), cross(p, A[2])) * dv
i33 = rho * dot(cross(p, A[3]), cross(p, A[3])) * dv
i12 = rho * dot(cross(p, A[1]), cross(p, A[2])) * dv
i13 = rho * dot(cross(p, A[1]), cross(p, A[3])) * dv
i23 = rho * dot(cross(p, A[2]), cross(p, A[3])) * dv

I11 = Int(Int(Int(i11, (a, 0, 2*pi)), (b, 0, 2*pi)), (c, 0, r2))
I22 = Int(Int(Int(i22, (a, 0, 2*pi)), (b, 0, 2*pi)), (c, 0, r2))
I33 = Int(Int(Int(i33, (a, 0, 2*pi)), (b, 0, 2*pi)), (c, 0, r2))
I12 = Int(Int(Int(i12, (a, 0, 2*pi)), (b, 0, 2*pi)), (c, 0, r2))
I13 = Int(Int(Int(i13, (a, 0, 2*pi)), (b, 0, 2*pi)), (c, 0, r2))
I23 = Int(Int(Int(i23, (a, 0, 2*pi)), (b, 0, 2*pi)), (c, 0, r2))

print 'I11 =', I11.subs({rho: m/V})
print 'I22 =', I22.subs({rho: m/V})
print 'I33 =', I33.subs({rho: m/V})
print 'I12 =', I12
print 'I13 =', I13
print 'I23 =', I23

max_stars_repo_name: certik/pydy

max_issues_repo_name: certik/pydy

max_forks_repo_name: certik/pydy

reason: from sympy


text:
import logging
import operator
from typing import Dict, List, Tuple

import numpy as np
from nuscenes.eval.detection.data_classes import DetectionMetrics

from datasetinsights.data.bbox import BBox3d

from .average_precision_config import (
    DIST_FCN,
    DIST_THS,
    LABEL_RANGE,
    MAX_BOXES_PER_SAMPLE,
    MEAN_AP_WEIGHT,
    MIN_PRECISION,
    MIN_RECALL,
)
from .base import EvaluationMetric

logger = logging.getLogger(__name__)

class DetectionConfig:
    """ Data class that specifies the detection evaluation settings. """

    def __init__(
        self,
        class_range: Dict[str, int] = LABEL_RANGE,
        dist_fcn: str = DIST_FCN,
        dist_ths: List[float] = DIST_THS,
        min_recall: float = MIN_RECALL,
        min_precision: float = MIN_PRECISION,
        max_boxes_per_sample: float = MAX_BOXES_PER_SAMPLE,
        mean_ap_weight: int = MEAN_AP_WEIGHT,
    ):
        self.class_range = class_range
        self.dist_fcn = dist_fcn
        self.dist_ths = dist_ths
        self.min_recall = min_recall
        self.min_precision = min_precision
        self.max_boxes_per_sample = max_boxes_per_sample
        self.mean_ap_weight = mean_ap_weight

        self.labels = self.class_range.keys()

    def __eq__(self, other):
        eq = True
        for key in self.serialize().keys():
            eq = eq and np.array_equal(getattr(self, key), getattr(other, key))
        return eq

    def serialize(self) -> dict:
        """ Serialize instance into json-friendly format. """
        return {
            "class_range": self.class_range,
            "dist_fcn": self.dist_fcn,
            "dist_ths": self.dist_ths,
            "min_recall": self.min_recall,
            "min_precision": self.min_precision,
            "max_boxes_per_sample": self.max_boxes_per_sample,
            "mean_ap_weight": self.mean_ap_weight,
        }

    @classmethod
    def deserialize(cls, content):
        """ Initialize from serialized dictionary. """
        return cls(
            content["class_range"],
            content["dist_fcn"],
            content["dist_ths"],
            content["min_recall"],
            content["min_precision"],
            content["max_boxes_per_sample"],
            content["mean_ap_weight"],
        )

def calc_ap(*, precision, min_recall: float, min_precision: float) -> float:
    """ Calculated average precision. """

    # assert 0 <= min_precision < 1
    # assert 0 <= min_recall <= 1

    prec = np.copy(precision)
    prec = prec[
        round(100 * min_recall) + 1 :
    ]  # Clip low recalls. +1 to exclude the min
    # recall bin.
    prec -= min_precision  # Clip low precision
    prec[prec < 0] = 0
    return float(np.mean(prec)) / (1.0 - min_precision)

def center_distance(*, gt_box: BBox3d, pred_box: BBox3d) -> float:
    """
    L2 distance between the box centers (xy only).
    :param gt_box: GT annotation sample.
    :param pred_box: Predicted sample.
    :return: L2 distance.
    """
    return np.linalg.norm(
        np.array(pred_box.translation[:2]) - np.array(gt_box.translation[:2])
    )

def _count_class_examples(
    boxes: Dict[str, Tuple[List[BBox3d], List[BBox3d]]], label
):
    npos = 0
    for image, (predicted_bbs, gt_bbs) in boxes.items():
        for gt_bounding_box in gt_bbs:
            if gt_bounding_box.label == label:
                npos += 1
    return npos

class AveragePrecision(EvaluationMetric):
    def __init__(self, config: DetectionConfig = None):
        self._num_examples = 0
        self.cfg = config or DetectionConfig()
        self.boxes = {}

    # todo need update method which accumulates serialized boxes
    def update(self, boxes):
        self.boxes.update(boxes)

    def reset(self):
        self.boxes = {}

    def _calculate_label_ap_simple(
        self,
        boxes: Dict[str, Tuple[List[BBox3d], List[BBox3d]]],
        label,
        dist_th,
    ):
        """
            calculate the average precision for a single class at a single
            distance threshold.
            :param boxes: keys -> (list of predicted boxes, list of ground
            truth boxes)
            :param label: name of the class used to calculate ap
            :param dist_th: distance threshold. Distance from center of the
            predicted box using birds-eye-view to
            ground truth box's center using birds eye view
            :return: average precesion for a single class at a single distance
             threshold
            """
        tp = []  # Accumulator of true positives
        fp = []  # Accumulator of false positives
        npos = _count_class_examples(boxes=boxes, label=label)
        for img, (predicted_bbs, gt_bbs) in boxes.items():
            predicted_bbs.sort(key=operator.attrgetter("score"), reverse=True)
            matches = set()
            for predicted_bb_index, predicted_box in enumerate(predicted_bbs):
                min_dist = np.inf
                for gt_idx, gt_box in enumerate(gt_bbs):
                    if gt_box.label == label and gt_idx not in matches:
                        this_distance = center_distance(
                            gt_box=gt_box, pred_box=predicted_box
                        )
                        if this_distance < min_dist:
                            min_dist = this_distance
                    is_match = min_dist < dist_th
                    if is_match:
                        tp.append(1)
                        fp.append(0)
                    else:
                        tp.append(0)
                        fp.append(1)
        if np.max(tp) == 0:
            return 0
        else:
            tp = np.cumsum(tp).astype(np.float)
            fp = np.cumsum(fp).astype(np.float)
            prec = tp / (fp + tp)
            rec = tp / float(npos)
            rec_interp = np.linspace(
                0, 1, 101
            )  # 101 steps, from 0% to 100% recall.
            prec = np.interp(rec_interp, rec, prec, right=0)
        ap = calc_ap(
            precision=prec,
            min_recall=self.cfg.min_recall,
            min_precision=self.cfg.min_precision,
        )
        return ap

    def compute(
        self, boxes: Dict[str, Tuple[List[BBox3d], List[BBox3d]]] = None
    ) -> Dict[str, float]:
        """
        calculate the mean ap for all classes over all distance thresholds
        defined in the config. The equation is
        described in the second figure in the nuscenes paper
        https://arxiv.org/pdf/1903.11027.pdf It is the normalized
        sum of the ROC curves for each class and distance.
        :param boxes: the predicted and ground truth bounding boxes per sample.
        :return: dictionary mapping each label to it's average precision
        (averaged across all distance thresholds for
        that label)
        """
        if boxes is None:
            boxes = self.boxes
        metrics = DetectionMetrics(self.cfg)
        for label in self.cfg.labels:
            for dist_th in self.cfg.dist_ths:
                ap = self._calculate_label_ap_simple(
                    boxes=boxes, dist_th=dist_th, label=label
                )
                metrics.add_label_ap(label, dist_th, ap)
        return metrics.mean_dist_aps

max_stars_repo_name: BlairLee/dataset-insights

max_issues_repo_name: BlairLee/dataset-insights

max_forks_repo_name: BlairLee/dataset-insights