{
"cells": [
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"\n",
"from scipy.odr import *\n",
"from scipy.stats import *\n",
"import numpy as np\n",
"import pandas as pd\n",
"import os\n",
"import time\n",
"import matplotlib.pyplot as plt\n",
"import ast\n",
"from multiprocessing import Pool, cpu_count\n",
"\n",
"import scipy\n",
"\n",
"from IPython import display\n",
"from matplotlib.patches import Rectangle\n",
"\n",
"from sklearn.metrics import mean_squared_error\n",
"import json\n",
"\n",
"import scipy.stats as st\n",
"from sklearn.metrics import r2_score\n",
"\n",
"\n",
"from matplotlib import cm\n",
"from mpl_toolkits.mplot3d import axes3d\n",
"import matplotlib.pyplot as plt\n",
"\n",
"import copy\n",
"\n",
"from sklearn.model_selection import LeaveOneOut, LeavePOut\n",
"\n",
"from multiprocessing import Pool\n",
"import cv2"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" userID | \n",
" Timestamp | \n",
" Current_Task | \n",
" Task_amount | \n",
" TaskID | \n",
" VersionID | \n",
" RepetitionID | \n",
" Actual_Data | \n",
" Is_Pause | \n",
" Image | \n",
"
\n",
" \n",
" \n",
" \n",
" 7919 | \n",
" 17 | \n",
" 1547138928692 | \n",
" 1 | \n",
" 680 | \n",
" 6 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | \n",
"
\n",
" \n",
" 7920 | \n",
" 17 | \n",
" 1547138928735 | \n",
" 1 | \n",
" 680 | \n",
" 6 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | \n",
"
\n",
" \n",
" 7921 | \n",
" 17 | \n",
" 1547138928773 | \n",
" 1 | \n",
" 680 | \n",
" 6 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | \n",
"
\n",
" \n",
" 7922 | \n",
" 17 | \n",
" 1547138928813 | \n",
" 1 | \n",
" 680 | \n",
" 6 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | \n",
"
\n",
" \n",
" 7923 | \n",
" 17 | \n",
" 1547138928861 | \n",
" 1 | \n",
" 680 | \n",
" 6 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" userID Timestamp Current_Task Task_amount TaskID VersionID \\\n",
"7919 17 1547138928692 1 680 6 2 \n",
"7920 17 1547138928735 1 680 6 2 \n",
"7921 17 1547138928773 1 680 6 2 \n",
"7922 17 1547138928813 1 680 6 2 \n",
"7923 17 1547138928861 1 680 6 2 \n",
"\n",
" RepetitionID Actual_Data Is_Pause \\\n",
"7919 0 True False \n",
"7920 0 True False \n",
"7921 0 True False \n",
"7922 0 True False \n",
"7923 0 True False \n",
"\n",
" Image \n",
"7919 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... \n",
"7920 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... \n",
"7921 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... \n",
"7922 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... \n",
"7923 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... "
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dfAll = pd.read_pickle(\"DataStudyCollection/AllData.pkl\")\n",
"df = dfAll[(dfAll.Actual_Data == True) & (dfAll.Is_Pause == False)]\n",
"df.head()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 39 s, sys: 5.78 s, total: 44.8 s\n",
"Wall time: 43.3 s\n"
]
}
],
"source": [
"%%time\n",
"def is_max(df):\n",
" df_temp = df.copy(deep=True)\n",
" max_version = df_temp.RepetitionID.max()\n",
" df_temp[\"IsMax\"] = np.where(df_temp.RepetitionID == max_version, True, False)\n",
" df_temp[\"MaxRepetition\"] = [max_version] * len(df_temp)\n",
" return df_temp\n",
"\n",
"df_grp = df.groupby([df.userID, df.TaskID, df.VersionID])\n",
"pool = Pool(cpu_count() - 1)\n",
"result_lst = pool.map(is_max, [grp for name, grp in df_grp])\n",
"df = pd.concat(result_lst)\n",
"pool.close()"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"df.Image = df.Image.apply(lambda x: x.reshape(27, 15))\n",
"df.Image = df.Image.apply(lambda x: x.clip(min=0, max=255))\n",
"df.Image = df.Image.apply(lambda x: x.astype(np.uint8))\n",
"df[\"ImageSum\"] = df.Image.apply(lambda x: np.sum(x))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"df.to_pickle(\"DataStudyCollection/dfFiltered.pkl\")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"recorded actual: 1010014, used data: 851455\n"
]
}
],
"source": [
"print(\"recorded actual: %s, used data: %s\" % (len(dfAll), len(df)))"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_pickle(\"DataStudyCollection/dfFiltered.pkl\")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" userID | \n",
" Timestamp | \n",
" Current_Task | \n",
" Task_amount | \n",
" TaskID | \n",
" VersionID | \n",
" RepetitionID | \n",
" Actual_Data | \n",
" Is_Pause | \n",
" Image | \n",
" IsMax | \n",
" MaxRepetition | \n",
" ImageSum | \n",
"
\n",
" \n",
" \n",
" \n",
" 291980 | \n",
" 1 | \n",
" 1,54515E+12 | \n",
" 33 | \n",
" 680 | \n",
" 0 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... | \n",
" True | \n",
" 0 | \n",
" 307 | \n",
"
\n",
" \n",
" 291981 | \n",
" 1 | \n",
" 1,54515E+12 | \n",
" 33 | \n",
" 680 | \n",
" 0 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... | \n",
" True | \n",
" 0 | \n",
" 222 | \n",
"
\n",
" \n",
" 291982 | \n",
" 1 | \n",
" 1,54515E+12 | \n",
" 33 | \n",
" 680 | \n",
" 0 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... | \n",
" True | \n",
" 0 | \n",
" 521 | \n",
"
\n",
" \n",
" 291983 | \n",
" 1 | \n",
" 1,54515E+12 | \n",
" 33 | \n",
" 680 | \n",
" 0 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... | \n",
" True | \n",
" 0 | \n",
" 318 | \n",
"
\n",
" \n",
" 291984 | \n",
" 1 | \n",
" 1,54515E+12 | \n",
" 33 | \n",
" 680 | \n",
" 0 | \n",
" 2 | \n",
" 0 | \n",
" True | \n",
" False | \n",
" [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... | \n",
" True | \n",
" 0 | \n",
" 373 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" userID Timestamp Current_Task Task_amount TaskID VersionID \\\n",
"291980 1 1,54515E+12 33 680 0 2 \n",
"291981 1 1,54515E+12 33 680 0 2 \n",
"291982 1 1,54515E+12 33 680 0 2 \n",
"291983 1 1,54515E+12 33 680 0 2 \n",
"291984 1 1,54515E+12 33 680 0 2 \n",
"\n",
" RepetitionID Actual_Data Is_Pause \\\n",
"291980 0 True False \n",
"291981 0 True False \n",
"291982 0 True False \n",
"291983 0 True False \n",
"291984 0 True False \n",
"\n",
" Image IsMax \\\n",
"291980 [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... True \n",
"291981 [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... True \n",
"291982 [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... True \n",
"291983 [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... True \n",
"291984 [[0, 2, 0, 0, 0, 0, 1, 2, 2, 3, 2, 1, 1, 1, 0]... True \n",
"\n",
" MaxRepetition ImageSum \n",
"291980 0 307 \n",
"291981 0 222 \n",
"291982 0 521 \n",
"291983 0 318 \n",
"291984 0 373 "
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.head()"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
"#Label if knuckle or finger\n",
"def f(row):\n",
" if row['TaskID'] < 17:\n",
" #val = \"Knuckle\"\n",
" val = 0\n",
" elif row['TaskID'] >= 17:\n",
" #val = \"Finger\"\n",
" val = 1\n",
" return val\n",
"df['InputMethod'] = df.apply(f, axis=1)\n",
"\n",
"def f(row):\n",
" if row['TaskID'] < 17:\n",
" val = \"Knuckle\"\n",
" elif row['TaskID'] >= 17:\n",
" val = \"Finger\"\n",
" return val\n",
"df['Input'] = df.apply(f, axis=1)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"#Svens new Blob detection\n",
"def detect_blobs(image, task):\n",
" #image = e.Image\n",
" large = np.ones((29,17), dtype=np.uint8)\n",
" large[1:28,1:16] = np.copy(image)\n",
" temp, thresh = cv2.threshold(cv2.bitwise_not(large), 200, 255, cv2.THRESH_BINARY)\n",
" contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n",
" contours = [a for a in contours if cv2.contourArea(a) > 8 and cv2.contourArea(a) < 255]\n",
" lstBlob = []\n",
" lstMin = []\n",
" lstMax = []\n",
" count = 0\n",
" contours.sort(key=lambda a: cv2.contourArea(a))\n",
" if len(contours) > 0:\n",
" # if two finger or knuckle\n",
" cont_count = 2 if task in [1, 6, 7, 18, 23, 24] and len(contours) > 1 else 1\n",
" for i in range(1, cont_count + 1):\n",
" max_contour = contours[-1 * i]\n",
" xmax, ymax = np.max(max_contour.reshape(len(max_contour),2), axis=0)\n",
" xmin, ymin = np.min(max_contour.reshape(len(max_contour),2), axis=0)\n",
" #croped_im = np.zeros((27,15))\n",
" blob = large[max(ymin - 1, 0):min(ymax + 1, large.shape[0]),max(xmin - 1, 0):min(xmax + 1, large.shape[1])]\n",
" #croped_im[0:blob.shape[0],0:blob.shape[1]] = blob\n",
" #return (1, [croped_im])\n",
" lstBlob.append(blob)\n",
" lstMin.append(xmax-xmin)\n",
" lstMax.append(ymax-ymin)\n",
" count = count + 1\n",
" return (count, lstBlob, lstMin, lstMax)\n",
" else:\n",
" return (0, [np.zeros((29, 19))], 0, 0)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 11.9 s, sys: 7.51 s, total: 19.4 s\n",
"Wall time: 18.6 s\n"
]
}
],
"source": [
"%%time\n",
"pool = Pool(os.cpu_count()-2)\n",
"temp_blobs = pool.starmap(detect_blobs, zip(df.Image, df.TaskID))\n",
"pool.close()"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"df[\"BlobCount\"] = [a[0] for a in temp_blobs]\n",
"df[\"BlobImages\"] = [a[1] for a in temp_blobs]\n",
"df[\"BlobW\"] = [a[2] for a in temp_blobs]\n",
"df[\"BlobH\"] = [a[3] for a in temp_blobs]"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0 710145\n",
"1 128117\n",
"2 13193\n",
"Name: BlobCount, dtype: int64"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.BlobCount.value_counts()"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"dfX = df[(df.BlobCount == 1)].copy(deep=True)\n",
"dfX.BlobImages = dfX.BlobImages.apply(lambda x : x[0])\n",
"dfX.BlobW = dfX.BlobW.apply(lambda x : x[0])\n",
"dfX.BlobH = dfX.BlobH.apply(lambda x : x[0])\n",
"\n",
"dfY = df[(df.BlobCount == 2)].copy(deep=True)\n",
"dfY.BlobImages = dfY.BlobImages.apply(lambda x : x[0])\n",
"dfY.BlobW = dfY.BlobW.apply(lambda x : x[0])\n",
"dfY.BlobH = dfY.BlobH.apply(lambda x : x[0])\n",
"\n",
"dfZ = df[(df.BlobCount == 2)].copy(deep=True)\n",
"dfZ.BlobImages = dfZ.BlobImages.apply(lambda x : x[1])\n",
"dfZ.BlobW = dfZ.BlobW.apply(lambda x : x[1])\n",
"dfZ.BlobH = dfZ.BlobH.apply(lambda x : x[1])\n",
"\n",
"df = dfX.append([dfY, dfZ])"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sample Size not Argumented: 154503\n"
]
}
],
"source": [
"print(\"Sample Size not Argumented:\", len(df))"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [],
"source": [
"df[\"BlobArea\"] = df[\"BlobW\"] * df[\"BlobH\"]"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"count 154503.0\n",
"mean 15.8\n",
"std 5.1\n",
"min 12.0\n",
"25% 12.0\n",
"50% 16.0\n",
"75% 16.0\n",
"max 110.0\n",
"Name: BlobArea, dtype: float64"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.BlobArea.describe().round(1)"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" count | \n",
" mean | \n",
" std | \n",
" min | \n",
" 25% | \n",
" 50% | \n",
" 75% | \n",
" max | \n",
"
\n",
" \n",
" Input | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" Finger | \n",
" 110839.0 | \n",
" 16.6 | \n",
" 5.3 | \n",
" 12.0 | \n",
" 12.0 | \n",
" 16.0 | \n",
" 16.0 | \n",
" 110.0 | \n",
"
\n",
" \n",
" Knuckle | \n",
" 43664.0 | \n",
" 13.7 | \n",
" 3.7 | \n",
" 12.0 | \n",
" 12.0 | \n",
" 12.0 | \n",
" 16.0 | \n",
" 72.0 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" count mean std min 25% 50% 75% max\n",
"Input \n",
"Finger 110839.0 16.6 5.3 12.0 12.0 16.0 16.0 110.0\n",
"Knuckle 43664.0 13.7 3.7 12.0 12.0 12.0 16.0 72.0"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df.groupby(\"Input\").BlobArea.describe().round(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[\"BlobSum\"] = df.BlobImages.apply(lambda x: np.sum(x))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.BlobSum.describe()"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"