{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "GRA KAMIEŃ-PAPIER-NOŻYCE LIVE"
      ],
      "metadata": {
        "id": "XwxmlCKWtO4c"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Część I."
      ],
      "metadata": {
        "id": "-BTDDfOctVLl"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "1. Import bibliotek"
      ],
      "metadata": {
        "id": "KDL7uawjTuOB"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "DfZ6VtnMrjTM",
        "outputId": "e93a66a7-b7e9-4146-edec-0ff6baee0b18"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "TF: 2.19.0\n"
          ]
        }
      ],
      "source": [
        "# standardowe biblioteki\n",
        "import os, sys, time, glob, random, base64, shutil, pathlib, io, json\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "from PIL import Image, ImageOps\n",
        "\n",
        "# Colab wyświetlanie\n",
        "from IPython.display import Javascript, display, Audio, Image as DImage\n",
        "from google.colab.output import eval_js\n",
        "\n",
        "# TensorFlow/Keras\n",
        "import tensorflow as tf\n",
        "from tensorflow.keras import layers, models\n",
        "from tensorflow.keras.utils import image_dataset_from_directory\n",
        "from tensorflow.keras.applications import MobileNetV2\n",
        "from tensorflow.keras.applications.mobilenet_v2 import preprocess_input as mnv2_preprocess\n",
        "from tensorflow.keras.models import load_model\n",
        "print(\"TF:\", tf.__version__)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "2. Konfiguracja środowiska [ROBIMY NA ZAJĘCIACH]"
      ],
      "metadata": {
        "id": "vYhESzkBTzN9"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Konfiguracja folderów projektu\n",
        "BASE_DIR = \"/content/projekt_kpn\"\n",
        "RAW_DIR  = BASE_DIR + \"/raw\"      # tu robimy zdjęcia z kamerki\n",
        "DATA_DIR = BASE_DIR + \"/data\"     # tu powstanie train/test do uczenia\n",
        "\n",
        "# Kategorie\n",
        "KATEGORIE = [\"Kamień\", \"Papier\", \"Nożyce\"]\n",
        "\n",
        "IMG_SIZE   = (224, 224)\n",
        "TRAIN_RATIO = 0.8  # 80% train, 20% test\n",
        "\n",
        "# Tworzymy katalogi na surowe zdjęcia\n",
        "for k in KATEGORIE:\n",
        "    os.makedirs(os.path.join(RAW_DIR, k), exist_ok=True)\n",
        "\n",
        "print(\"Kategorie:\", KATEGORIE)\n",
        "print(\"RAW_DIR:\", RAW_DIR)\n"
      ],
      "metadata": {
        "id": "GFaAOo8DsOGq"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "3. Funkcja zbierania danych (wykorzystanie Javascript)"
      ],
      "metadata": {
        "id": "8smB1HypUElK"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Kamerka z JavaScript: robienie wielu zdjęć do wybranej kategorii\n",
        "\n",
        "def zbierz_proby(kategoria, ile_min=20, jakosc=0.9, lustrzane=True):\n",
        "    # proste GUI w JS: przycisk \"Zrób zdjęcie\" i \"Zakończ\"\n",
        "    target = os.path.join(RAW_DIR, kategoria)\n",
        "    os.makedirs(target, exist_ok=True)\n",
        "\n",
        "    js = Javascript('''\n",
        "      async function sesja(nMin, q) {\n",
        "        const div = document.createElement('div');\n",
        "        const btn = document.createElement('button');\n",
        "        btn.textContent = 'Zrób zdjęcie';\n",
        "        btn.style.fontSize = '16px';\n",
        "        btn.style.marginRight = '8px';\n",
        "        const done = document.createElement('button');\n",
        "        done.textContent = 'Zakończ';\n",
        "        done.disabled = true;\n",
        "        const info = document.createElement('span');\n",
        "        info.style.marginLeft = '10px';\n",
        "        info.textContent = '0 / ' + nMin;\n",
        "\n",
        "        div.appendChild(btn);\n",
        "        div.appendChild(done);\n",
        "        div.appendChild(info);\n",
        "\n",
        "        const video = document.createElement('video');\n",
        "        video.style.display = 'block';\n",
        "        video.style.maxWidth = '480px';\n",
        "        const stream = await navigator.mediaDevices.getUserMedia({video:true});\n",
        "        document.body.appendChild(div);\n",
        "        div.appendChild(video);\n",
        "        video.srcObject = stream;\n",
        "        await video.play();\n",
        "        google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);\n",
        "\n",
        "        const images = [];\n",
        "        btn.onclick = () => {\n",
        "          const canvas = document.createElement('canvas');\n",
        "          canvas.width = video.videoWidth;\n",
        "          canvas.height = video.videoHeight;\n",
        "          canvas.getContext('2d').drawImage(video, 0, 0);\n",
        "          images.push(canvas.toDataURL('image/jpeg', q));\n",
        "          info.textContent = images.length + ' / ' + nMin;\n",
        "          if (images.length >= nMin) done.disabled = false;\n",
        "        };\n",
        "\n",
        "        await new Promise(resolve => done.onclick = resolve);\n",
        "        stream.getTracks().forEach(t => t.stop());\n",
        "        div.remove();\n",
        "        return images;\n",
        "      }\n",
        "    ''')\n",
        "    display(js)\n",
        "    data_urls = eval_js(f\"sesja({ile_min}, {jakosc})\")\n",
        "\n",
        "\n",
        "    zapisane = 0\n",
        "    for i, d in enumerate(data_urls):\n",
        "        raw = base64.b64decode(d.split(',')[1])\n",
        "        fname = os.path.join(target, f\"{kategoria}_{int(time.time()*1000)}_{i:03d}.jpg\")\n",
        "        with open(fname, 'wb') as f:\n",
        "            f.write(raw)\n",
        "        zapisane += 1\n",
        "\n",
        "        if lustrzane:\n",
        "            img = Image.open(io.BytesIO(raw)).convert('RGB')\n",
        "            img = ImageOps.mirror(img)\n",
        "            fname2 = os.path.join(target, f\"{kategoria}_{int(time.time()*1000)}_{i:03d}_m.jpg\")\n",
        "            img.save(fname2, \"JPEG\", quality=int(jakosc*100))\n",
        "            zapisane += 1\n",
        "\n",
        "    print(\"Zapisano:\", zapisane, \"plików do\", target)\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "Mmwc2yGHsPeD"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "4. Zbieranie danych do modelu [ROBIMY NA ZAJĘCIACH]"
      ],
      "metadata": {
        "id": "g4AozACQbA-h"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "zbierz_proby(\"Papier\", ile_min=50, lustrzane=True)"
      ],
      "metadata": {
        "id": "XDJnDN5utv2e"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "zbierz_proby(\"Kamień\", ile_min=50, lustrzane=True)"
      ],
      "metadata": {
        "id": "ONyV1B0eUP-Z"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "zbierz_proby(\"Nożyce\", ile_min=50, lustrzane=True)"
      ],
      "metadata": {
        "id": "F4HujcHVURKv"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "5. Podział danych"
      ],
      "metadata": {
        "id": "g0GY0K_TbGdI"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Podział na train/test - kopiowanie obrazów do konkretnych folderów\n",
        "\n",
        "def podziel_train_test():\n",
        "    if os.path.exists(DATA_DIR):\n",
        "        shutil.rmtree(DATA_DIR)\n",
        "    for split in [\"train\", \"test\"]:\n",
        "        for k in KATEGORIE:\n",
        "            os.makedirs(os.path.join(DATA_DIR, split, k), exist_ok=True)\n",
        "\n",
        "    for k in KATEGORIE:\n",
        "        pliki = glob.glob(os.path.join(RAW_DIR, k, \"*\"))\n",
        "        pliki = [p for p in pliki if os.path.isfile(p)]\n",
        "        random.shuffle(pliki)\n",
        "        n = len(pliki)\n",
        "        n_train = int(n * TRAIN_RATIO)\n",
        "        train_files = pliki[:n_train]\n",
        "        test_files  = pliki[n_train:]\n",
        "        for src in train_files:\n",
        "            shutil.copy2(src, os.path.join(DATA_DIR, \"train\", k, os.path.basename(src)))\n",
        "        for src in test_files:\n",
        "            shutil.copy2(src, os.path.join(DATA_DIR, \"test\", k, os.path.basename(src)))\n",
        "    print(\"Dane w\", DATA_DIR)\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "HzgQqbFmsRIR"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "podziel_train_test()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "_YjLCZHzudes",
        "outputId": "f213dfef-e767-431c-9b62-fc86c69ba73a"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Zrobione. Dane w /content/projekt_kpn/data\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "6. Wczytanie danych i trening modelu [ROBIMY NA ZAJĘCIACH]"
      ],
      "metadata": {
        "id": "HtfPUCkec0le"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Funkcje wczytania danych i budowy modelu\n",
        "AUTOTUNE = tf.data.AUTOTUNE\n",
        "BATCH_SIZE = 32\n",
        "\n",
        "def wczytaj_dane():\n",
        "    train_ds = image_dataset_from_directory(\n",
        "        os.path.join(DATA_DIR, \"train\"),\n",
        "        image_size=IMG_SIZE,\n",
        "        batch_size=BATCH_SIZE,\n",
        "        label_mode='categorical',\n",
        "        shuffle=True\n",
        "    )\n",
        "    test_ds = image_dataset_from_directory(\n",
        "        os.path.join(DATA_DIR, \"test\"),\n",
        "        image_size=IMG_SIZE,\n",
        "        batch_size=BATCH_SIZE,\n",
        "        label_mode='categorical',\n",
        "        shuffle=False\n",
        "    )\n",
        "    return train_ds.prefetch(AUTOTUNE), test_ds.prefetch(AUTOTUNE), train_ds.class_names\n",
        "\n",
        "def zbuduj_model(n_klas):\n",
        "\n",
        "  #odporność modelu\n",
        "    wej = layers.Input(shape=IMG_SIZE + (3,))\n",
        "    x = layers.RandomFlip(\"horizontal\")(wej)\n",
        "    x = layers.RandomRotation(0.05)(x)\n",
        "    x = layers.RandomZoom(0.1)(x)\n",
        "    x = mnv2_preprocess(x)\n",
        "\n",
        "    baza = MobileNetV2(include_top=False, input_tensor=x, weights=\"imagenet\", pooling=\"avg\")\n",
        "    baza.trainable = False\n",
        "\n",
        "    x = layers.Dropout(0.2)(baza.output)\n",
        "    wyj = layers.Dense(n_klas, activation=\"softmax\")(x)\n",
        "\n",
        "    model = models.Model(wej, wyj)\n",
        "    model.compile(optimizer=\"adam\", loss=\"categorical_crossentropy\", metrics=[\"accuracy\"])\n",
        "    return model\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "UDPyo2wusTk8"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Trenujemy model\n",
        "train_ds, test_ds, class_names = wczytaj_dane()\n",
        "print(\"class_names:\", class_names)\n",
        "\n",
        "model = zbuduj_model(len(class_names))\n",
        "hist = model.fit(train_ds, validation_data=test_ds, epochs=6)\n",
        "loss, acc = model.evaluate(test_ds)\n",
        "print(\"Test acc:\", acc)\n",
        "\n",
        "plt.plot(hist.history['accuracy'], label='train')\n",
        "plt.plot(hist.history['val_accuracy'], label='val')\n",
        "plt.legend(); plt.title(\"Dokładność\"); plt.show()\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "eu3KYVNRsU3k"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "7. Predykcja LIVE z kamerki"
      ],
      "metadata": {
        "id": "R8b7kEJ7dAAO"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Funkcja kamerki"
      ],
      "metadata": {
        "id": "xOkPkwf65LYn"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Predykcja z kamerki\n",
        "\n",
        "\n",
        "def take_photo(nazwa=\"foto.jpg\", jakosc=0.9):\n",
        "    js = Javascript('''\n",
        "      async function f(q) {\n",
        "        const div = document.createElement('div');\n",
        "        const b = document.createElement('button');\n",
        "        b.textContent = 'Zrób zdjęcie';\n",
        "        b.style.fontSize = '16px';\n",
        "        b.style.marginBottom = '8px';\n",
        "        div.appendChild(b);\n",
        "\n",
        "        const video = document.createElement('video');\n",
        "        video.style.display='block';\n",
        "        video.style.maxWidth='480px';\n",
        "        const stream = await navigator.mediaDevices.getUserMedia({video:true});\n",
        "        document.body.appendChild(div);\n",
        "        div.appendChild(video);\n",
        "        video.srcObject = stream;\n",
        "        await video.play();\n",
        "        google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);\n",
        "\n",
        "        await new Promise(resolve => b.onclick = resolve);\n",
        "        const canvas = document.createElement('canvas');\n",
        "        canvas.width = video.videoWidth;\n",
        "        canvas.height = video.videoHeight;\n",
        "        canvas.getContext('2d').drawImage(video, 0, 0);\n",
        "        stream.getTracks().forEach(t => t.stop());\n",
        "        div.remove();\n",
        "        return canvas.toDataURL('image/jpeg', q);\n",
        "      }\n",
        "    ''')\n",
        "    display(js)\n",
        "    data = eval_js(f\"f({jakosc})\")\n",
        "    raw = base64.b64decode(data.split(',')[1])\n",
        "    with open(nazwa, 'wb') as f:\n",
        "        f.write(raw)\n",
        "    return nazwa\n",
        "\n",
        "def przygotowanie(path, rozmiar=IMG_SIZE, mirror=False):\n",
        "    img = Image.open(path).convert(\"RGB\")\n",
        "    w, h = img.size\n",
        "    m = min(w, h)\n",
        "    img = img.crop(((w-m)//2, (h-m)//2, (w+m)//2, (h+m)//2)).resize(rozmiar)\n",
        "    if mirror:\n",
        "        img = ImageOps.mirror(img)\n",
        "    x = np.asarray(img).astype(\"float32\")\n",
        "    x = np.expand_dims(x, 0)\n",
        "    return x\n",
        "\n",
        "\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "JFWtq5JgsXpy"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "Funkcja kamerka + klasyfikacja"
      ],
      "metadata": {
        "id": "g-BmMKgf5Ip8"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Funkcja TOP3\n",
        "def przewidz(path, mirror=False, topk=3):\n",
        "    x = przygotowanie(path, mirror=mirror)\n",
        "    p = model.predict(x, verbose=0)[0]\n",
        "    idx = int(np.argmax(p))\n",
        "    etyk = class_names[idx]              # surowa etykieta (nazwa folderu)\n",
        "    tytul = etyk.replace(\"_\",\" \").title()\n",
        "    pew = float(p[idx])\n",
        "\n",
        "    print(f\"Przewidziano: {tytul} ({etyk}), pewność: {pew:.2f}\")\n",
        "    top = np.argsort(p)[-topk:][::-1]\n",
        "    print(\"Top-3:\")\n",
        "    for i in top:\n",
        "        print(f\"  {class_names[i]}: {p[i]:.3f}\")"
      ],
      "metadata": {
        "id": "ng2Hetnn5GLn"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "foto = take_photo(\"foto.jpg\")\n",
        "przewidz(foto, mirror=False)"
      ],
      "metadata": {
        "id": "5mBlXoi3sXc_"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "Część II."
      ],
      "metadata": {
        "id": "d7efXGYLtkTn"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "1. Automatyczne zdjęcie w 3s"
      ],
      "metadata": {
        "id": "rZABvz3LdFOl"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# ODLICZANIE 3 SEKUND + AUTOMATYCZNE ZDJĘCIE\n",
        "\n",
        "\n",
        "def take_photo_countdown_3s(nazwa=\"foto_3s.jpg\", jakosc=0.9):\n",
        "    js = Javascript('''\n",
        "      async function snap3s(q){\n",
        "        const wrap = document.createElement('div');\n",
        "        const video = document.createElement('video');\n",
        "        const btn = document.createElement('button');\n",
        "        const overlay = document.createElement('div');\n",
        "\n",
        "        btn.textContent = 'Start';\n",
        "        btn.style.fontSize = '16px';\n",
        "        btn.style.margin = '8px 0';\n",
        "\n",
        "        video.style.display = 'block';\n",
        "        video.style.maxWidth = '480px';\n",
        "        video.style.borderRadius = '8px';\n",
        "\n",
        "        overlay.style.position = 'fixed';\n",
        "        overlay.style.left = 0;\n",
        "        overlay.style.top = 0;\n",
        "        overlay.style.right = 0;\n",
        "        overlay.style.bottom = 0;\n",
        "        overlay.style.display = 'flex';\n",
        "        overlay.style.alignItems = 'center';\n",
        "        overlay.style.justifyContent = 'center';\n",
        "        overlay.style.fontFamily = 'sans-serif';\n",
        "        overlay.style.fontSize = '120px';\n",
        "        overlay.style.fontWeight = '700';\n",
        "        overlay.style.color = 'white';\n",
        "        overlay.style.background = 'rgba(0,0,0,0.35)';\n",
        "        overlay.style.zIndex = 999999;\n",
        "        overlay.style.visibility = 'hidden';\n",
        "\n",
        "        document.body.appendChild(wrap);\n",
        "        wrap.appendChild(video);\n",
        "        wrap.appendChild(btn);\n",
        "        document.body.appendChild(overlay);\n",
        "\n",
        "        const stream = await navigator.mediaDevices.getUserMedia({video:true});\n",
        "        video.srcObject = stream;\n",
        "        await video.play();\n",
        "        google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);\n",
        "\n",
        "        await new Promise(r => btn.onclick = r);\n",
        "\n",
        "        overlay.style.visibility = 'visible';\n",
        "        for (let s = 3; s >= 1; s--) {\n",
        "          overlay.textContent = String(s);\n",
        "          await new Promise(r => setTimeout(r, 1000));\n",
        "        }\n",
        "        overlay.textContent = 'GO!';\n",
        "        await new Promise(r => setTimeout(r, 250));\n",
        "\n",
        "        const canvas = document.createElement('canvas');\n",
        "        canvas.width = video.videoWidth;\n",
        "        canvas.height = video.videoHeight;\n",
        "        canvas.getContext('2d').drawImage(video, 0, 0);\n",
        "\n",
        "        stream.getTracks().forEach(t => t.stop());\n",
        "        wrap.remove();\n",
        "        overlay.remove();\n",
        "\n",
        "        return canvas.toDataURL('image/jpeg', q);\n",
        "      }\n",
        "    ''')\n",
        "    display(js)\n",
        "    data = eval_js(f'snap3s({jakosc})')\n",
        "    raw = base64.b64decode(data.split(',')[1])\n",
        "    with open(nazwa, 'wb') as f:\n",
        "        f.write(raw)\n",
        "    return nazwa"
      ],
      "metadata": {
        "id": "v8MdIxkFtg-o"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Wywołaj funkcje robienia zdjęcia z opóźnieniem oraz funkcję predykcji [TO DO]\n"
      ],
      "metadata": {
        "id": "i_pLS7jotlim"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "2. Logika gry z komputerem"
      ],
      "metadata": {
        "id": "wExHcnfusgOQ"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "\n",
        "# Ustawienia gry [TO DO]\n",
        "\n",
        "# Logika wygranej [TO DO]\n",
        "\n",
        "\n",
        "# Predykcja ruchu gracza z fotki [TO DO]\n",
        "\n",
        "\n",
        "\n",
        "# Pętla 3 rundy [TO DO]\n",
        "\n",
        "\n",
        "# Podsumowanie po 3 rundach [TO DO]\n"
      ],
      "metadata": {
        "id": "iS1JnxMsshRd"
      },
      "execution_count": null,
      "outputs": []
    }
  ]
}