[
  {
    "path": ".gitignore",
    "content": "/virtualC"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.9-slim \n\nWORKDIR /\nCOPY . .\nRUN apt-get update && apt-get install ffmpeg libsm6 libxext6  -y\nRUN apt-get update \\\n    && apt-get install -y --no-install-recommends \\\n        build-essential \\\n        libopencv-dev \\\n    && rm -rf /var/lib/apt/lists/*\nRUN pip install -r requirements.txt\nEXPOSE 8080\n\nCMD [\"gunicorn\", \"--bind\", \"0.0.0.0:8080\", \"app:app\"]"
  },
  {
    "path": "README.md",
    "content": "A virtual calculator build using OpenCV.\nDEMO: https://www.youtube.com/watch?v=M8ZjUq8QD10\n"
  },
  {
    "path": "app.py",
    "content": "from flask import Flask,render_template,Response, jsonify , request\nimport cv2 as cv \nfrom main import serve\nimport numpy as np\nimport base64\nfrom flask_cors import CORS\n\n\n\n\n\napp=Flask(__name__)\nCORS(app)\n\n\n# camera=cv.VideoCapture(2)\n# camera.set(3,1280) #width\n# camera.set(4,720) #height\nhistory=[]\n\n\n\n\n# def generate_frames():\n#     while True:\n       \n#         success,frame=camera.read()\n#         if not success:\n#             break\n#         else:\n#             try:\n#                 frame,finEq=serve(frame)\n#             except:\n#                 pass\n#             if finEq!=\"\":\n#                 global history\n#                 history.append(finEq)\n#                 print(history)\n#             ret,buffer=cv.imencode('.jpg',frame)\n#             frame=buffer.tobytes()\n#             yield(b'--frame\\r\\n'\n#                 b'Content-Type: image/jpeg\\r\\n\\r\\n' + frame + b'\\r\\n')\n        \n\n@app.route('/process_frame',methods=['POST'])\ndef process_frame():\n    try:\n        file=request.files['frame']\n        \n        file_byte=np.frombuffer(file.read(),np.uint8)\n        frame=cv.imdecode(file_byte,cv.IMREAD_COLOR)\n        frame,finEq=serve(frame)\n        # print(\"GOT PROCESSED FILE\")\n        _,buffer=cv.imencode('.jpg',frame)\n        processed_frame=base64.b64encode(buffer).decode('utf-8')\n        if finEq!=\"\":\n            global history\n            history.append(finEq)\n            \n            print(history)\n        return jsonify(success=True, frame =processed_frame)\n    except Exception as e:\n        print(e)\n        return jsonify(success=False,message=str(e)) \n        \n\n@app.route('/get_history')\ndef update_history():\n    global history\n    print(history)\n    return jsonify(success=True,history=history)\n\n@app.route('/')\ndef index():\n    # return \"Starting\"\n    global history\n    return render_template('index.html',history=history)\n\n@app.route('/video')\ndef video_function():\n    return Response(generate_frames(),mimetype='multipart/x-mixed-replace; boundary=frame')\n\n\nif __name__==\"__main__\":\n    app.run(debug=True)"
  },
  {
    "path": "main.py",
    "content": "import cv2 as cv\r\nfrom cvzone.HandTrackingModule import HandDetector\r\n\r\nclass Button:\r\n    def __init__(self, pos, width, height, value):\r\n        self.pos = pos\r\n        self.width = width\r\n        self.height = height\r\n        self.value = value\r\n\r\n    def draw(self, frame):\r\n        cv.rectangle(frame, self.pos, (self.pos[0] + self.width, self.pos[1] + self.height),\r\n                     (225, 255, 225), cv.FILLED)\r\n        cv.rectangle(frame, self.pos, (self.pos[0] + self.width, self.pos[1] + self.height),\r\n                     (50, 50, 225), 4)\r\n        cv.putText(frame, self.value, (self.pos[0] + 20, self.pos[1] + 50),\r\n                   cv.FONT_HERSHEY_PLAIN, 2, (50, 50, 225), 2)\r\n\r\n    def clickCheck(self, x, y, frame):\r\n        if self.pos[0] < x < self.pos[0] + self.width and self.pos[1] < y < self.pos[1] + self.height:\r\n            cv.rectangle(frame, self.pos, (self.pos[0] + self.width, self.pos[1] + self.height),\r\n                         (255, 255, 225), cv.FILLED)\r\n            cv.rectangle(frame, self.pos, (self.pos[0] + self.width, self.pos[1] + self.height),\r\n                         (0, 0, 50), 4)\r\n            cv.putText(frame, self.value, (self.pos[0] + 20, self.pos[1] + 50),\r\n                       cv.FONT_HERSHEY_PLAIN, 2, (50, 50, 25), 6)\r\n            return True\r\n        return False\r\n\r\ndetect = HandDetector(detectionCon=0.9, maxHands=1)\r\n\r\nbutListVal = [\r\n    ['1', '2', '3', '+'],\r\n    ['4', '5', '6', '-'],\r\n    ['7', '8', '9', '*'],\r\n    ['0', '/', '.', '=']\r\n]\r\n\r\nbutList = [Button((x * 100 + 800, y * 100 + 150), 100, 100, butListVal[y][x]) for x in range(4) for y in range(4)]\r\n\r\nmyEq = \"\"\r\ndelayCounter = 0\r\nfinEq = \"\"\r\n\r\n\r\ndef serve(frame):\r\n    global myEq, delayCounter, finEq\r\n    frame = cv.flip(frame, 1)\r\n\r\n    hand, frame = detect.findHands(frame, flipType=False)\r\n\r\n    cv.rectangle(frame, (800, 50), (1200, 150), (225, 255, 225), cv.FILLED)\r\n    cv.rectangle(frame, (800, 50), (1200, 150), (50, 50, 225), 4)\r\n    for button in butList:\r\n        button.draw(frame)\r\n\r\n\r\n    ev=\"\"\r\n\r\n    if hand:\r\n        lmList = hand[0]['lmList']\r\n        length, info, frame = detect.findDistance(lmList[8][:2], lmList[12][:2], frame)\r\n        x, y = lmList[8][:2]\r\n\r\n        if length < 35:\r\n            for i, button in enumerate(butList):\r\n                if button.clickCheck(x, y, frame) and delayCounter == 0:\r\n                    myVal = butListVal[int(i % 4)][int(i / 4)]\r\n                    if myVal == '=' and myEq != \"\":\r\n                        finEq = str(eval(myEq))\r\n                        ev=myEq+\"=\"+finEq\r\n                        myEq = \"\"\r\n                    elif myVal != '=':\r\n                        myEq += myVal\r\n                        finEq = \"\"\r\n                    delayCounter = 1\r\n\r\n    if delayCounter != 0:\r\n        delayCounter += 1\r\n        if delayCounter > 10:\r\n            delayCounter = 0\r\n\r\n    display_text = finEq if myEq == \"\" else myEq\r\n    cv.putText(frame, display_text, (820, 114), cv.FONT_HERSHEY_PLAIN, 3, (50, 50, 225), 3)\r\n    if ev!=\"\":\r\n        return frame,ev\r\n    else:\r\n        return frame,\"\"\r\n"
  },
  {
    "path": "requirements.txt",
    "content": "absl-py==2.1.0\nattrs==23.2.0\nblinker==1.8.2\ncffi==1.16.0\nclick==8.1.7\ncolorama==0.4.6\ncontourpy==1.2.1\ncvzone==1.6.1\ncycler==0.12.1\nFlask==3.0.3\nFlask-Cors==4.0.1\nflatbuffers==24.3.25\nfonttools==4.53.0\ngunicorn==22.0.0\nimportlib_metadata==8.0.0\nimportlib_resources==6.4.0\nitsdangerous==2.2.0\njax==0.4.30\njaxlib==0.4.30\nJinja2==3.1.4\nkiwisolver==1.4.5\nMarkupSafe==2.1.5\nmatplotlib==3.9.1\nmediapipe==0.10.14\nml-dtypes==0.4.0\nnumpy==2.0.0\nopencv-contrib-python==4.10.0.84\nopencv-python==4.10.0.84\nopt-einsum==3.3.0\npackaging==24.1\npillow==10.4.0\nprotobuf==4.25.3\npycparser==2.22\npyparsing==3.1.2\npython-dateutil==2.9.0.post0\nscipy==1.13.1\nsix==1.16.0\nsounddevice==0.4.7\nWerkzeug==3.0.3\nzipp==3.19.2\n"
  },
  {
    "path": "static/serve.js",
    "content": "const processedVideo = document.getElementById('processed-video');\n\n// Request access to the camera\nnavigator.mediaDevices.getUserMedia({ video: true })\n    .then(stream => {\n        const videoTracks = stream.getVideoTracks();\n        const track = videoTracks[0];\n        const imageCapture = new ImageCapture(track);\n        processFrames(imageCapture);\n        \n\n    })\n    .catch(error => {\n        console.error('Error accessing the camera: ', error);\n    });\n\nfunction processFrames(imageCapture) {\n    const fps = 10; // Frames per second\n    setInterval(() => {\n        imageCapture.grabFrame()\n            .then(imageBitmap => {\n                const canvas = document.createElement('canvas');\n                const context = canvas.getContext('2d');\n                canvas.width = 1280;\n                canvas.height = 900;\n                context.drawImage(imageBitmap, 0, 0, canvas.width, canvas.height);\n                canvas.toBlob(blob => {\n                    const formData = new FormData();\n                    formData.append('frame', blob, 'frame.jpg');\n                    fetch('/process_frame', {\n                        method: 'POST',\n                        body: formData\n                    })\n                    .then(response => response.json())\n                    .then(data => {\n                        if (data.success) {\n                            // Display the processed frame\n                            processedVideo.src = 'data:image/jpeg;base64,' + data.frame;\n                        } else {\n                            console.error('Error processing frame: ', data.message);\n                        }\n                    })\n                    .catch(error => {\n                        console.error('Error processing frame: ', error);\n                    });\n                }, 'image/jpeg');\n            })\n            .catch(error => {\n                console.error('Error grabbing frame: ', error);\n            });\n    }, 1000 / fps);\n}\n\nfunction updateHisory(){\n    fetch('/get_history').then(response => response.json()).then(data => {\n        if (data.success) {\n            const historyList = document.getElementById('history-list');\n            console.log(data.history);\n            historyList.innerHTML = '';\n            data.history.forEach(item => {\n                const listItem = document.createElement('li');\n                listItem.textContent = item;\n                historyList.appendChild(listItem);\n            });\n        } else {\n            console.error('Error getting history: ', data.message);\n        }\n    }).catch(error => {\n        console.error('Error getting history: ', error);\n    });\n}\n\nsetInterval(updateHisory, 2000);"
  },
  {
    "path": "templates/index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n        <title></title>\n        <meta name=\"description\" content=\"\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        <link rel=\"stylesheet\" href=\"\">\n        <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH\" crossorigin=\"anonymous\">\n    </head>\n    <body class=\"bg-dark text-light\">\n        <header class=\"d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom mx-5 px-5 rounded \">\n            <div class=\"col-md-3 mb-2 mb-md-0\">\n              <a href=\"/\" class=\"d-inline-flex link-body-emphasis text-decoration-none\">\n                <svg class=\"bi\" width=\"40\" height=\"32\" role=\"img\" aria-label=\"Bootstrap\"><use xlink:href=\"#bootstrap\"></use></svg>\n              </a>\n            </div>\n      \n            <div class=\"nav col-12 col-md-auto mb-2 justify-content-center mb-md-0\">\n              <h3 style=\"font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif \" ><strong> Virtual Calculator </strong></h3>\n            </div>\n      \n            <div class=\"col-md-3 text-end d-flex align-items-center justify-content-around\">\n                <p style=\"font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; margin: 0;\">Connect with me:</p>\n                <div class=\"d-flex gap-2\">\n                    <a href=\"https://www.linkedin.com/in/hazsyl1/\">\n                        <button type=\"button\" class=\"btn btn-primary\">LinkedIn</button>\n                    </a>\n                    <a href=\"https://github.com/HazSyl1\">\n                        <button type=\"button\" class=\"btn btn-primary\">GitHub</button>\n                    </a>\n                </div>\n          </header>\n          <main style=\"display: flex;flex-direction: column; justify-content: center; align-items: center; height: 100%; \">\n            <!-- <img style=\"display: block;\" src=\"{{url_for('video_function')}}\" alt=\"Video Footage\" width=\"50%\" height=\"auto\"> -->\n            <!-- <video style=\"border:2px solid #333; border-radius: 8px; margin:10px;\" id=\"video\"  autoplay></video> -->\n            <img id=\"processed-video\" alt=\"Processed Frame\"  width=\"50%\" height=\"auto\">\n            <script src=\"{{ url_for('static',filename='serve.js') }}\"></script>\n\n\n          <p  style=\"font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; margin: 0;\">Refresh to restart!</p>\n          <div style=\"margin-top: 20px;\">\n            <ul id=\"history-list\" style=\"list-style: none; padding-left: 0;\">\n              {% for item in history %}\n                <li style=\"font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; margin: 0;\">{{item}}</li>\n              {% endfor %}\n            </ul>\n          </div>\n          </main>\n    </body>\n</html>"
  }
]