{
  "cells": [
    {
      "cell_type": "raw",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "# UrFall Multimodal Dataset - Comprehensive Demo\n",
        "\n",
        "This notebook demonstrates:\n",
        "- Download/select subsets (features, accelerometer, depth, RGB, video)\n",
        "- Load pre-extracted features and raw accelerometer\n",
        "- Create sliding windows and extract gait/media features\n",
        "- Light EDA and sample visualizations\n",
        "- Simple RF classification combining signals\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Setup imports\n",
        "import os\n",
        "import io\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import zipfile\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "from gaitsetpy.dataset import UrFallLoader\n",
        "from gaitsetpy.dataset.utils import download_urfall_data\n",
        "from gaitsetpy.features import GaitFeatureExtractor, UrFallMediaFeatureExtractor\n",
        "from gaitsetpy.preprocessing import create_preprocessing_pipeline\n",
        "from sklearn.ensemble import RandomForestClassifier\n",
        "from sklearn.model_selection import train_test_split\n",
        "from sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay\n",
        "\n",
        "DATA_DIR = os.path.join('.', 'urfall_data')\n",
        "os.makedirs(DATA_DIR, exist_ok=True)\n",
        "print(DATA_DIR)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 1) Loader and metadata\n",
        "loader = UrFallLoader()\n",
        "print(loader.name, loader.description)\n",
        "print('Supported types:', loader.metadata['data_types'])\n",
        "print('Sampling frequency (camera):', loader.metadata['sampling_frequency'])\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 2) Ensure features CSVs are present (falls + ADLs)\n",
        "download_urfall_data(DATA_DIR, data_types=['features'], use_falls=True, use_adls=True)\n",
        "\n",
        "# 3) Load features\n",
        "data, names = loader.load_data(DATA_DIR, data_types=['features'], use_falls=True, use_adls=True)\n",
        "print(f\"Loaded {len(data)} feature DataFrames:\", names)\n",
        "if data:\n",
        "    display(data[0].head())\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 4) Sliding windows on features\n",
        "windows = loader.create_sliding_windows(data, names, window_size=30, step_size=15)\n",
        "print(f\"Window sets: {len(windows)}\")\n",
        "if windows:\n",
        "    print('Example window names:', [w['name'] for w in windows[:3]])\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 5) Download a tiny subset of raw accelerometer and extract gait features\n",
        "subset = ['fall-01', 'adl-01']\n",
        "download_urfall_data(DATA_DIR, sequences=subset, data_types=['accelerometer', 'synchronization'], use_falls=True, use_adls=True)\n",
        "acc_data, acc_names = loader.load_data(DATA_DIR, data_types=['accelerometer'], sequences=subset, use_falls=True, use_adls=True)\n",
        "print(acc_names)\n",
        "\n",
        "acc_windows = loader.create_sliding_windows(acc_data, acc_names, window_size=100, step_size=50)\n",
        "fs_acc = loader.metadata.get('accelerometer_frequency', 100)\n",
        "extractor = GaitFeatureExtractor(verbose=False)\n",
        "flat_sensor_windows = [entry for w in acc_windows for entry in w['windows'] if entry['name'] not in ['labels','activity_id']]\n",
        "acc_feats = extractor.extract_features(flat_sensor_windows, fs=fs_acc)\n",
        "print(f\"Extracted {len(acc_feats)} gait sensor feature dicts\")\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 6) Pull a few frames from depth/RGB archives and extract media features\n",
        "download_urfall_data(DATA_DIR, sequences=subset, data_types=['depth','rgb','video'], use_falls=True, use_adls=True, max_workers=6)\n",
        "\n",
        "def analyze_zip(paths, grayscale):\n",
        "    media_extractor = UrFallMediaFeatureExtractor(verbose=False)\n",
        "    windows = []\n",
        "    for seq, path in list(paths.items()):\n",
        "        try:\n",
        "            with zipfile.ZipFile(path, 'r') as zf:\n",
        "                pngs = [n for n in zf.namelist() if n.lower().endswith('.png')][:12]\n",
        "                frames = []\n",
        "                for name in pngs:\n",
        "                    with zf.open(name) as f:\n",
        "                        arr = plt.imread(io.BytesIO(f.read()))\n",
        "                        frames.append(arr)\n",
        "                if frames:\n",
        "                    windows.append({'name': seq, 'data': frames})\n",
        "        except Exception as e:\n",
        "            print('warn', e)\n",
        "    feats = media_extractor.extract_features(windows, fs=loader.metadata['sampling_frequency'], grayscale=grayscale)\n",
        "    return {f['name']: f['features'] for f in feats}\n",
        "\n",
        "depth_paths = loader.get_file_paths(DATA_DIR, 'depth', sequences=subset)\n",
        "rgb_paths = loader.get_file_paths(DATA_DIR, 'rgb', sequences=subset)\n",
        "video_paths = loader.get_file_paths(DATA_DIR, 'video', sequences=subset)\n",
        "\n",
        "depth_feats = analyze_zip(depth_paths, grayscale=True)\n",
        "rgb_feats = analyze_zip(rgb_paths, grayscale=False)\n",
        "\n",
        "# Video optional (requires cv2); skip if not available\n",
        "try:\n",
        "    import cv2\n",
        "    media_extractor = UrFallMediaFeatureExtractor(verbose=False)\n",
        "    windows = []\n",
        "    for seq, path in list(video_paths.items()):\n",
        "        cap = cv2.VideoCapture(path)\n",
        "        if not cap.isOpened():\n",
        "            continue\n",
        "        frames = []\n",
        "        for _ in range(120):\n",
        "            ret, frame = cap.read()\n",
        "            if not ret:\n",
        "                break\n",
        "            frames.append(frame)\n",
        "        cap.release()\n",
        "        if frames:\n",
        "            windows.append({'name': seq, 'data': frames})\n",
        "    video_feats_list = media_extractor.extract_features(windows, fs=loader.metadata['sampling_frequency'], grayscale=True)\n",
        "    video_feats = {f['name']: f['features'] for f in video_feats_list}\n",
        "except Exception as e:\n",
        "    print('Video feature extraction skipped:', e)\n",
        "    video_feats = {}\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 7) Build a simple classification dataset and train RF\n",
        "X_list, y_list = [], []\n",
        "# From accelerometer windows: simple stats per window\n",
        "for item in acc_windows:\n",
        "    per_name = {entry['name']: entry['data'] for entry in item['windows']}\n",
        "    if 'activity_id' in per_name:\n",
        "        labels_windows = per_name['activity_id']\n",
        "        window_labels = []\n",
        "        for wlab in labels_windows:\n",
        "            vals, counts = np.unique(wlab, return_counts=True)\n",
        "            window_labels.append(int(vals[np.argmax(counts)]))\n",
        "    else:\n",
        "        continue\n",
        "    for name_key, windows_arr in per_name.items():\n",
        "        if name_key in ['labels','activity_id']:\n",
        "            continue\n",
        "        for idx, w in enumerate(windows_arr):\n",
        "            arr_w = np.ravel(np.array(w, dtype=np.float32))\n",
        "            vec = [float(np.mean(arr_w)), float(np.std(arr_w)), float(np.max(arr_w)-np.min(arr_w))]\n",
        "            X_list.append(vec)\n",
        "            y_list.append(window_labels[idx] if idx < len(window_labels) else 0)\n",
        "\n",
        "# From media features: aggregate\n",
        "for seq, feat in depth_feats.items():\n",
        "    X_list.append([feat.get('mean_intensity', 0.0), feat.get('std_intensity', 0.0)])\n",
        "    y_list.append(1 if seq.startswith('fall-') else 0)\n",
        "for seq, feat in rgb_feats.items():\n",
        "    X_list.append([feat.get('mean_intensity', 0.0), feat.get('std_intensity', 0.0)])\n",
        "    y_list.append(1 if seq.startswith('fall-') else 0)\n",
        "for seq, feat in (video_feats or {}).items():\n",
        "    X_list.append([feat.get('motion_mean', 0.0), feat.get('motion_std', 0.0), feat.get('brightness_mean', 0.0)])\n",
        "    y_list.append(1 if seq.startswith('fall-') else 0)\n",
        "\n",
        "max_dim = max(len(v) for v in X_list) if X_list else 0\n",
        "pad = lambda v, d: v if len(v)==d else (v[:d] if len(v)>d else v + [0.0]*(d-len(v)))\n",
        "X = np.array([pad(list(map(float, v)), max_dim) for v in X_list], dtype=np.float32) if X_list else np.empty((0,0), dtype=np.float32)\n",
        "y = np.array(y_list, dtype=np.int64) if y_list else np.empty((0,), dtype=np.int64)\n",
        "print('X, y shapes:', X.shape, y.shape)\n",
        "\n",
        "if X.size and len(np.unique(y))>=2 and len(y)>=4:\n",
        "    pipeline = create_preprocessing_pipeline(['clipping'], clipping={'min_val': float(np.min(X)), 'max_val': float(np.max(X))})\n",
        "    Xp = pipeline(X)\n",
        "    X_train, X_test, y_train, y_test = train_test_split(Xp, y, test_size=0.3, random_state=42, stratify=y)\n",
        "    clf = RandomForestClassifier(n_estimators=100, random_state=42)\n",
        "    clf.fit(X_train, y_train)\n",
        "    y_pred = clf.predict(X_test)\n",
        "    acc = accuracy_score(y_test, y_pred)\n",
        "    print(f'RandomForest accuracy: {acc:.3f}')\n",
        "    cm = confusion_matrix(y_test, y_pred)\n",
        "    ConfusionMatrixDisplay(confusion_matrix=cm).plot(values_format='d')\n",
        "    plt.title('Confusion Matrix - UrFall RF')\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "else:\n",
        "    print('Not enough labeled samples for training.')\n"
      ]
    },
    {
      "cell_type": "raw",
      "metadata": {
        "vscode": {
          "languageId": "raw"
        }
      },
      "source": [
        "## Notes\n",
        "- Depth/RGB/video downloads are sizable; the demo samples only a few frames.\n",
        "- If `cv2` is not available, video features are skipped.\n",
        "- You can extend feature vectors and try `gaitsetpy` neural models similarly to other notebooks.\n"
      ]
    }
  ],
  "metadata": {
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
