{"id":22923507,"url":"https://github.com/ams-osram/tmf8820_21_28_app_keystone","last_synced_at":"2025-04-01T14:44:39.917Z","repository":{"id":259411713,"uuid":"877150703","full_name":"ams-OSRAM/tmf8820_21_28_app_keystone","owner":"ams-OSRAM","description":"Measure angle of wall in front of TMF8820/21/28 to allow keystone correction in a projector application","archived":false,"fork":false,"pushed_at":"2024-10-24T08:18:32.000Z","size":686,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-07T09:23:45.352Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ams-OSRAM.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-23T07:11:50.000Z","updated_at":"2024-12-12T13:48:46.000Z","dependencies_parsed_at":"2024-10-25T06:45:02.725Z","dependency_job_id":"25b5063b-d283-4881-a972-c4013cc6d2ab","html_url":"https://github.com/ams-OSRAM/tmf8820_21_28_app_keystone","commit_stats":null,"previous_names":["ams-osram/tmf8820_21_28_app_keystone"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2Ftmf8820_21_28_app_keystone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2Ftmf8820_21_28_app_keystone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2Ftmf8820_21_28_app_keystone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2Ftmf8820_21_28_app_keystone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ams-OSRAM","download_url":"https://codeload.github.com/ams-OSRAM/tmf8820_21_28_app_keystone/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246659545,"owners_count":20813329,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-14T08:15:53.758Z","updated_at":"2025-04-01T14:44:39.691Z","avatar_url":"https://github.com/ams-OSRAM.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Measure angle of wall in front of TMF8820/21/28 to allow keystone correction in a projector application\n\nScript runs interactively and connects to the [TMF8820/21/28 EVM](https://ams-osram.com/products/boards-kits-accessories/kits/ams-tmf882x-evm-db-demo-evaluation-kit)\n\n![application_running.png](./application_running.png)\n[Video](https://github.com/user-attachments/assets/16422a6b-2bad-4d12-b3fb-853ec489fe00)\n\nHowto execute:\n1. You need to have Python3 installed; inside Python3 it needs matplotlib library installed\n2. EVM of [TMF882X](https://ams-osram.com/products/boards-kits-accessories/kits/ams-tmf882x-evm-db-demo-evaluation-kit) installed and EVM GUI running before starting the script\n3. Double click 'keystone.py' or execute 'keystone.ipynb' in a JupyterLab environment\n\nOperation:\n- Hold the EVM in front of a wall\n- Rotate the EVM along its vertical or lateral axis and you should see the 3d View representing the EVM being rotated in sync with the rotation of the EVM\n\nDocumentation:\n- README.md or keystone.ipynb\n\n\n## Measure angle of wall facing TMF8820/TMF8821\n![math.drawio.png](./math.drawio.png)\n\nand use 3x3 mode\n\n### We have\nb, c and $\\alpha$  \nc = distance of center zone  \nb = distance of edge zone  \n$\\alpha$ defined by SPAD map - angle between center zone and edge zone\n\n### We want\n$\\phi$\n\n### Calculation\nCalculate a with \u003chttps://www.arndt-bruenner.de/mathe/10/kosinussatz.htm\u003e\n\n$a^2 = b^2 + c^2 – b c \\cos \\alpha$\n\n$a = \\sqrt{ b^2 + c^2 – b c \\cos \\alpha} $\n\nApply sinussatz \u003chttps://de.wikipedia.org/wiki/Sinussatz\u003e\n\n$\\frac{a}{\\sin \\alpha} = \\frac{c}{\\sin \\beta} = \\frac{b}{\\sin \\gamma }$\n\n$\\frac{b}{\\sin \\gamma} = \\frac{a}{\\sin \\alpha }$\n\n$\\frac{\\sin \\gamma }{b} = \\frac{\\sin \\alpha}{a}$\n\n$\\sin \\gamma = \\frac{b \\sin \\alpha }{a}$\n\n$\\gamma = \\arcsin \\frac{b \\sin \\alpha }{a} $\n\n$\\phi = 90^\\circ - \\gamma$  \n\n\n```python\n# *****************************************************************************\n# Copyright (c) [2024] ams-OSRAM AG                                           *\n# All rights are reserved.                                                    *\n#                                                                             *\n# FOR FULL LICENSE TEXT SEE LICENSE.TXT                                       *\n# *****************************************************************************\n#\n# Version 1v1\n# ptr / 23.Oct 2024\n```\n\n\n```python\nimport math\nimport socket \nimport matplotlib.pyplot as plt\nimport matplotlib.animation as animation\nimport matplotlib.gridspec as gridspec\n\n\ndef calc_angle(center, edge, alpha):\n    '''\n    Args:\n        Calculate angle of triangle to a perpendicular wall where\n\n        alpha = angle between dist1 and dist2 (defined by SPAD mask) in degrees\n        center = distance of center zone in [mm]\n        edge = distance of edge zone in [mm]\n\n    Returns:\n        angle in degrees (not radians)\n    '''\n    try:\n        # apply law of cosines to get third length of triangle\n        a = math.sqrt(center**2 + edge**2 \n                      - 2 * center * edge * math.cos(math.radians(alpha)))\n        # apply law of sines to calculate angles in triangle\n        gamma = math.degrees(math.asin(edge / a * math.sin(math.radians(alpha))))\n        phi = 90 - gamma\n        # note: asin is ambiguous - cannot check if we are above or below perpendicular\n        # check this wiht pythagoras\n        if edge**2 \u003e center**2 + a**2:\n            phi = -phi\n        return (phi)\n    except ZeroDivisionError:\n        return (0)\n```\n\n\n```python\ntof_id = None # tof unique ID\n\n\ndef get_data_from_EVM_GUI(sock) -\u003e (str, int, int, int, int):\n    '''Process information from TMF882X EVM GUI and extract data as needed\n    Args:\n        sock -- an open socket for the connection to the EVM GUI; connection errors need to be handled in calling function\n\n    Returns:    \n        string description -- a textual description for displaying the result or any error\n        int xtalk -- integer with amount of crosstalk; returns 0 in case of error\n        int background -- the amount of background light, should be much lower than xtalk\n        int iterations -- number of iterations for this measurement\n        obj -- object data; array of integers of distance / confidence for each channel\n    '''\n    xtalk = [0]*10  # initialize\n    background = [0]*10\n    iterations = 0\n    obj = None  # initialize\n    sock.sendall(b'(m0)')  # run measurement command 'm'\n    while True:\n        global tof_id\n        try:\n            s = str(sock.recv(10000), 'utf-8')\n            entries = s.split('\\r\\n')  # we could have read several lines, so process them one by one\n            for item in entries:\n                try:\n                    data = item.split(';')\n                    if data[0] == '#VER':\n                        tof_id = f'TMF882X ID {data[1]} - App {data[2]} - Drv {data[3]}'\n                    if data[0][0:6] == '#HLONG':  # histograms\n                        # do whatever needs to be done with histograms... only an example is shown below\n                        entry = int(data[0][7]) # which histogram?\n                        pt_hist = [int(x) for x in data[1:]]  # convert to int list\n                        xtalk[entry] = max(pt_hist[5:20])  # search for the crosstalk peak\n                        # background light calculation: max(avg bins 0-8)\n                        background[entry] = sum(pt_hist[0:8])/8\n                    if data[0] == '#OBJ':  # OBJ is always the last entry\n                        obj = [int(x) for x in data[5:]]  # convert to int list removing other information\n                        return (f'XTalk {xtalk} BG {background}', xtalk, background, iterations, obj)\n                    if data[0] == '#ITT':  # iterations\n                        iterations = int(data[2])\n                except (ValueError, IndexError) as e:\n                    pass  # to ignore header lines as they raise an exception if converted to int()\n        except socket.timeout:\n            tof_id = ''\n            return('No device connected', 0, 0, 0, None)\n\n\ndef pick_best(obj):\n    '''Check if there is a second target and use this if it has higher confidence\n    Args:\n        obj -- object data; array of integers of distance / confidence for each channel\n    Returns:\n        single selected object\n    '''\n    if obj[1] \u003e obj[3]:  # compare confidence first/second target\n        return obj[0]  # return first target\n    else:\n        return obj[2]  # return second target1\n\n\ndef animate(i, axs, sock):\n    '''Run animantion\n    Args:\n        i -- needed for animation call\n        axs -- axes for drawings\n        sock -- socket for communication\n    '''\n    description, xtalk, background, iterations, obj = get_data_from_EVM_GUI(sock)\n    for i in range(10):\n        axs[i].clear()\n        axs[i].set_xticks([])  # hide axes\n        axs[i].set_yticks([])  # hide axes\n    ax_info = axs[9]\n    ax_info.set(frame_on=False)\n    ax3d = axs[10]\n    ax3d.axis('off')\n    ax3d.set_xlim(-1.5, 2.5)\n    ax3d.set_ylim(-0.2, 1.2)\n    if obj:\n        try:\n            for i in range(9): # iterate over zones\n                index = i*4  # index into objects\n                if (obj[index]\u003e0):\n                    axs[i].annotate(f'{obj[index]}mm', (0.5, 0.8), ha='center', va='center')   # distance\n                    axs[i].annotate(f'conf {obj[index+1]}', (0.5, 0.6), ha='center', va='center') # confidence\n                if (obj[index+2]\u003e0):  # second object?\n                    axs[i].annotate(f\"{obj[index+2]}mm\", (0.5, 0.4), ha='center', va='center')   # distance\n                    axs[i].annotate(obj[index+3], (0.5, 0.2), ha='center', va='center') # confidence   \n#                # add background and crosstalk - uncomment if you want to show this parameters as well\n#                axs[i].annotate(f'xtalk {xtalk[i+1]}', (0.5, 0.4), ha='center', va='center')\n#                axs[i].annotate(f'ambient bg. {int(background[i+1])}', (0.5, 0.2), ha='center', va='center')\n\n            # now fill the information frame\n            angle_zone_left_right = 33/3 # angle between center and left / right zone in degrees\n            angle_zone_up_down = 32/3    # angle between center and up / down zone in degrees\n\n            center = pick_best(obj[4*4:])\n            top = pick_best(obj[1*4:])\n            bottom = pick_best(obj[7*4:])\n            left = pick_best(obj[3*4:])\n            right = pick_best(obj[5*4:])\n\n            top_angle = calc_angle(center, top, angle_zone_up_down)\n            bottom_angle = calc_angle(center, bottom, angle_zone_up_down)\n            left_angle = calc_angle(center, left, angle_zone_left_right)\n            right_angle = calc_angle(center, right, angle_zone_left_right)\n            ax_info.annotate(tof_id, (0.5, 0.7), ha='center', va='center')\n            text_color = 'darkblue'\n            max_angle_deviation = 15\n            if (top_angle + bottom_angle \u003e max_angle_deviation) or (left_angle + right_angle \u003e max_angle_deviation):\n                text_color = 'red'\n            ax_info.annotate(f'(Angles: left {left_angle:6.1f}° right {right_angle:6.1f}°    top {top_angle:6.1f}° bottom {bottom_angle:6.1f}°)',\n                             (0.5, 0.5), ha='center', va='center')\n            ax_info.annotate(f'Vertical axis (yaw) {(left_angle-right_angle)/2:6.1f}°      Lateral axis (pitch) {(top_angle-bottom_angle)/2:6.1f}°',\n                             (0.5, 0.3), ha='center', va='center', color=text_color)\n            # now show 3d animation\n            ax3d.bar3d([0], [0], [0], [1], [1], [1], shade=True, color='cornflowerblue')\n            ax3d.view_init(elev=(top_angle-bottom_angle)/2, azim=(right_angle-left_angle)/2)\n\n        except IndexError:\n            pass   # ignore it...\n    else:\n        ax_info.annotate('No Device Connected', (0.1, 0.5), ha='left', va='center', color='red')\n\n\nglobal ani\nani  = None  # need to define global to avoid closing animation\nsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) \nhost = \"localhost\"  # the EVM GUI should be run on the same computer\nport = 39998  # ToF EVM Automation port \n\ntry:\n    sock.connect((host, port))\n    sock.settimeout(1)  # 1s timeout\n    sock.sendall(b'(i4000)') # 4 M iterations\n    # fig, axs = plt.subplots(3, 3, figsize=(7, 5)) # create figure\n    fig = plt.figure(constrained_layout=True, figsize=(10, 6))\n    spec = gridspec.GridSpec(ncols=5, nrows=4, figure=fig)\n    axs = [None]*11  # reserve array\n    for i in range(9):\n        axs[i] = fig.add_subplot(spec[int(i/3) + 1, i % 3])\n    axs[9] = fig.add_subplot(spec[0, :])  # information row - spans whole first row\n    axs[10] = fig.add_subplot(spec[1:, -2:], projection='3d') # 3d view; last column\n    axs[10].set_title('3d View')\n\n    animate(0, axs, sock) # first call to see if we have errors\n\n    ani = animation.FuncAnimation(fig, animate, fargs=[axs, sock], interval=300)\n    plt.show()\n\nexcept socket.error as e:\n    print(f'Please start TMF882X EVM GUI and connect TMF882X before starting this script - Connection Error: {e}')\n    input('Press enter to quit')\n\n# need to close sock when animnation is finished\nsock.close()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fams-osram%2Ftmf8820_21_28_app_keystone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fams-osram%2Ftmf8820_21_28_app_keystone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fams-osram%2Ftmf8820_21_28_app_keystone/lists"}